diff --git a/Cargo.toml b/Cargo.toml index f9d9b5253..eff58208c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ anyhow = "1.0" cc = "1.0.49" codespan-reporting = "0.9" cxxbridge-macro = { version = "=0.2.9", path = "macro" } +itertools = "0.9" link-cplusplus = "1.0" proc-macro2 = { version = "1.0", features = ["span-locations"] } quote = "1.0" diff --git a/README.md b/README.md index f51a1ac47..3f7399a64 100644 --- a/README.md +++ b/README.md @@ -84,8 +84,10 @@ mod ffi { // Functions implemented in C++. fn make_demo(appname: &str) -> UniquePtr; - fn get_name(thing: &ThingC) -> &CxxString; fn do_thing(state: SharedThing); + + // Methods implemented in C++. + fn get_name(self: &ThingC) -> &CxxString; } extern "Rust" { @@ -95,6 +97,9 @@ mod ffi { // Functions implemented in Rust. fn print_r(r: &ThingR); + + // Methods implemented in Rust. + fn print(self: &ThingR); } } ``` @@ -335,8 +340,6 @@ This is still early days for CXX; I am releasing it as a minimum viable product to collect feedback on the direction and invite collaborators. Here are some of the facets that I still intend for this project to tackle: -- [ ] Support associated methods: `extern "Rust" { fn f(self: &Struct); }` -- [ ] Support C++ member functions - [ ] Support structs with type parameters - [ ] Support async functions diff --git a/demo-cxx/demo.cc b/demo-cxx/demo.cc index cd447ea0e..bc0d97685 100644 --- a/demo-cxx/demo.cc +++ b/demo-cxx/demo.cc @@ -9,13 +9,19 @@ ThingC::ThingC(std::string appname) : appname(std::move(appname)) {} ThingC::~ThingC() { std::cout << "done with ThingC" << std::endl; } +const std::string &ThingC::get_name() const { + std::cout << "I'm a C++ method!" << std::endl; + return this->appname; +} + std::unique_ptr make_demo(rust::Str appname) { return std::unique_ptr(new ThingC(std::string(appname))); } -const std::string &get_name(const ThingC &thing) { return thing.appname; } - -void do_thing(SharedThing state) { print_r(*state.y); } +void do_thing(SharedThing state) { + print_r(*state.y); + state.y->print(); +} } // namespace example } // namespace org diff --git a/demo-cxx/demo.h b/demo-cxx/demo.h index fafc4743a..885293fb4 100644 --- a/demo-cxx/demo.h +++ b/demo-cxx/demo.h @@ -12,12 +12,13 @@ class ThingC { ~ThingC(); std::string appname; + + const std::string &get_name() const; }; struct SharedThing; std::unique_ptr make_demo(rust::Str appname); -const std::string &get_name(const ThingC &thing); void do_thing(SharedThing state); } // namespace example diff --git a/demo-rs/src/main.rs b/demo-rs/src/main.rs index 66dfc7997..713a1d1a7 100644 --- a/demo-rs/src/main.rs +++ b/demo-rs/src/main.rs @@ -11,13 +11,15 @@ mod ffi { type ThingC; fn make_demo(appname: &str) -> UniquePtr; - fn get_name(thing: &ThingC) -> &CxxString; + fn get_name(self: &ThingC) -> &CxxString; fn do_thing(state: SharedThing); + } extern "Rust" { type ThingR; fn print_r(r: &ThingR); + fn print(self: &ThingR); } } @@ -27,9 +29,15 @@ fn print_r(r: &ThingR) { println!("called back with r={}", r.0); } +impl ThingR { + fn print(&self) { + println!("method called back with r={}", self.0); + } +} + fn main() { let x = ffi::make_demo("demo of cxx::bridge"); - println!("this is a {}", ffi::get_name(x.as_ref().unwrap())); + println!("this is a {}", x.as_ref().unwrap().get_name()); ffi::do_thing(ffi::SharedThing { z: 222, diff --git a/gen/write.rs b/gen/write.rs index 322ca0871..b8d17dfd2 100644 --- a/gen/write.rs +++ b/gen/write.rs @@ -2,7 +2,8 @@ use crate::gen::namespace::Namespace; use crate::gen::out::OutFile; use crate::gen::{include, Opt}; use crate::syntax::atom::Atom::{self, *}; -use crate::syntax::{Api, ExternFn, Signature, Struct, Type, Types, Var}; +use crate::syntax::{Api, ExternFn, ExternType, Receiver, Signature, Struct, Type, Types, Var}; +use itertools::Itertools; use proc_macro2::Ident; pub(super) fn gen( @@ -44,10 +45,30 @@ pub(super) fn gen( } } + let methods_for_type = apis.iter().filter_map(|api| match api { + Api::RustFunction(efn) => match &efn.sig.receiver { + Some(rcvr) => Some((&rcvr.ident, efn)), + _ => None, + }, + _ => None, + }).into_group_map(); + for api in apis { - if let Api::Struct(strct) = api { - out.next_section(); - write_struct(out, strct); + match api { + Api::Struct(strct) => { + out.next_section(); + write_struct(out, strct); + } + Api::RustType(ety) => { + match methods_for_type.get(&ety.ident) { + Some(methods) => { + out.next_section(); + write_struct_with_methods(out, ety, methods); + }, + _ => {} + } + } + _ => {} } } @@ -300,6 +321,23 @@ fn write_struct_using(out: &mut OutFile, ident: &Ident) { writeln!(out, "using {} = {};", ident, ident); } +fn write_struct_with_methods(out: &mut OutFile, ety: &ExternType, methods: &Vec<&ExternFn>) { + for line in ety.doc.to_string().lines() { + writeln!(out, "//{}", line); + } + writeln!(out, "struct {} final {{", ety.ident); + writeln!(out, " {}() = delete;", ety.ident); + writeln!(out, " {}(const {}&) = delete;", ety.ident, ety.ident); + for method in methods { + write!(out, " "); + let sig = &method.sig; + let local_name = method.ident.to_string(); + write_rust_function_shim_decl(out, &local_name, sig, None, false); + writeln!(out, ";"); + } + writeln!(out, "}};"); +} + fn write_exception_glue(out: &mut OutFile, apis: &[Api]) { let mut has_cxx_throws = false; for api in apis { @@ -326,9 +364,16 @@ fn write_cxx_function_shim(out: &mut OutFile, efn: &ExternFn, types: &Types) { } else { write_extern_return_type_space(out, &efn.ret, types); } - write!(out, "{}cxxbridge02${}(", out.namespace, efn.ident); + let receiver_type = match &efn.receiver { + Some(base) => base.ident.to_string(), + None => "_".to_string(), + }; + write!(out, "{}cxxbridge02${}${}(", out.namespace, receiver_type, efn.ident); + if let Some(base) = &efn.receiver { + write!(out, "{} *__receiver$", base.ident); + } for (i, arg) in efn.args.iter().enumerate() { - if i > 0 { + if i > 0 || efn.receiver.is_some() { write!(out, ", "); } if arg.ty == RustString { @@ -347,14 +392,27 @@ fn write_cxx_function_shim(out: &mut OutFile, efn: &ExternFn, types: &Types) { writeln!(out, ") noexcept {{"); write!(out, " "); write_return_type(out, &efn.ret); - write!(out, "(*{}$)(", efn.ident); + match &efn.receiver { + None => write!(out, "(*{}$)(", efn.ident), + Some(base) => write!(out, "({}::*{}$)(", base.ident, efn.ident), + } for (i, arg) in efn.args.iter().enumerate() { if i > 0 { write!(out, ", "); } write_type(out, &arg.ty); } - writeln!(out, ") = {};", efn.ident); + write!(out, ")"); + match &efn.receiver { + Some(Receiver { mutability: None, ident: _ }) => write!(out, " const"), + _ => {}, + } + write!(out, " = "); + match &efn.receiver { + None => write!(out, "{}", efn.ident), + Some(base) => write!(out, "&{}::{}", base.ident, efn.ident), + } + writeln!(out, ";"); write!(out, " "); if efn.throws { writeln!(out, "::rust::Str::Repr throw$;"); @@ -377,7 +435,10 @@ fn write_cxx_function_shim(out: &mut OutFile, efn: &ExternFn, types: &Types) { } _ => {} } - write!(out, "{}$(", efn.ident); + match &efn.receiver { + None => write!(out, "{}$(", efn.ident), + Some(_) => write!(out, "(__receiver$->*{}$)(", efn.ident), + } for (i, arg) in efn.args.iter().enumerate() { if i > 0 { write!(out, ", "); @@ -452,7 +513,11 @@ fn write_function_pointer_trampoline( } fn write_rust_function_decl(out: &mut OutFile, efn: &ExternFn, types: &Types) { - let link_name = format!("{}cxxbridge02${}", out.namespace, efn.ident); + let receiver_type = match &efn.receiver { + Some(base) => base.ident.to_string(), + None => "_".to_string(), + }; + let link_name = format!("{}cxxbridge02${}${}", out.namespace, receiver_type, efn.ident); let indirect_call = false; write_rust_function_decl_impl(out, &link_name, efn, types, indirect_call); } @@ -471,6 +536,10 @@ fn write_rust_function_decl_impl( } write!(out, "{}(", link_name); let mut needs_comma = false; + if let Some(base) = &sig.receiver { + write!(out, "{} &__receiver$", base.ident); + needs_comma = true; + } for arg in &sig.args { if needs_comma { write!(out, ", "); @@ -500,20 +569,26 @@ fn write_rust_function_shim(out: &mut OutFile, efn: &ExternFn, types: &Types) { writeln!(out, "//{}", line); } let local_name = efn.ident.to_string(); - let invoke = format!("{}cxxbridge02${}", out.namespace, efn.ident); + let receiver_type = match &efn.receiver { + Some(base) => base.ident.to_string(), + None => "_".to_string(), + }; + let invoke = format!("{}cxxbridge02${}${}", out.namespace, receiver_type, efn.ident); let indirect_call = false; write_rust_function_shim_impl(out, &local_name, efn, types, &invoke, indirect_call); } -fn write_rust_function_shim_impl( +fn write_rust_function_shim_decl( out: &mut OutFile, local_name: &str, sig: &Signature, - types: &Types, - invoke: &str, + receiver: Option<&Receiver>, indirect_call: bool, ) { write_return_type(out, &sig.ret); + if let Some(base) = receiver { + write!(out, "{}::", base.ident); + } write!(out, "{}(", local_name); for (i, arg) in sig.args.iter().enumerate() { if i > 0 { @@ -532,6 +607,21 @@ fn write_rust_function_shim_impl( if !sig.throws { write!(out, " noexcept"); } +} + +fn write_rust_function_shim_impl( + out: &mut OutFile, + local_name: &str, + sig: &Signature, + types: &Types, + invoke: &str, + indirect_call: bool, +) { + if out.header && sig.receiver.is_some() { + // We've already defined this inside the struct. + return; + } + write_rust_function_shim_decl(out, local_name, sig, sig.receiver.as_ref(), indirect_call); if out.header { writeln!(out, ";"); } else { @@ -570,8 +660,11 @@ fn write_rust_function_shim_impl( write!(out, "::rust::Str::Repr error$ = "); } write!(out, "{}(", invoke); + if let Some(_) = &sig.receiver { + write!(out, "*this"); + } for (i, arg) in sig.args.iter().enumerate() { - if i > 0 { + if i > 0 || sig.receiver.is_some() { write!(out, ", "); } match &arg.ty { diff --git a/macro/src/expand.rs b/macro/src/expand.rs index 146d21b10..2d3166a20 100644 --- a/macro/src/expand.rs +++ b/macro/src/expand.rs @@ -123,6 +123,13 @@ fn expand_cxx_type(ety: &ExternType) -> TokenStream { fn expand_cxx_function_decl(namespace: &Namespace, efn: &ExternFn, types: &Types) -> TokenStream { let ident = &efn.ident; + let receiver = efn.receiver.iter().map(|base| { + let ident = &base.ident; + match base.mutability { + None => quote!(_: &#ident), + Some(_) => quote!(_: &mut #ident), + } + }); let args = efn.args.iter().map(|arg| { let ident = &arg.ident; let ty = expand_extern_type(&arg.ty); @@ -136,6 +143,7 @@ fn expand_cxx_function_decl(namespace: &Namespace, efn: &ExternFn, types: &Types quote!(#ident: #ty) } }); + let all_args = receiver.chain(args); let ret = if efn.throws { quote!(-> ::cxx::private::Result) } else { @@ -146,11 +154,15 @@ fn expand_cxx_function_decl(namespace: &Namespace, efn: &ExternFn, types: &Types let ret = expand_extern_type(efn.ret.as_ref().unwrap()); outparam = Some(quote!(__return: *mut #ret)); } - let link_name = format!("{}cxxbridge02${}", namespace, ident); + let receiver_type = match &efn.receiver { + Some(base) => base.ident.to_string(), + None => "_".to_string(), + }; + let link_name = format!("{}cxxbridge02${}${}", namespace, receiver_type, ident); let local_name = format_ident!("__{}", ident); quote! { #[link_name = #link_name] - fn #local_name(#(#args,)* #outparam) #ret; + fn #local_name(#(#all_args,)* #outparam) #ret; } } @@ -158,7 +170,12 @@ fn expand_cxx_function_shim(namespace: &Namespace, efn: &ExternFn, types: &Types let ident = &efn.ident; let doc = &efn.doc; let decl = expand_cxx_function_decl(namespace, efn, types); - let args = &efn.args; + let receiver = efn.receiver.iter().map(|base| match base.mutability { + None => quote!(&self), + Some(_) => quote!(&mut self), + }); + let args = efn.args.iter().map(|arg| quote!(#arg)); + let all_args = receiver.chain(args); let ret = if efn.throws { let ok = match &efn.ret { Some(ret) => quote!(#ret), @@ -169,7 +186,8 @@ fn expand_cxx_function_shim(namespace: &Namespace, efn: &ExternFn, types: &Types expand_return_type(&efn.ret) }; let indirect_return = indirect_return(efn, types); - let vars = efn.args.iter().map(|arg| { + let receiver_var = efn.receiver.iter().map(|_| quote!(self)); + let arg_vars = efn.args.iter().map(|arg| { let var = &arg.ident; match &arg.ty { Type::Ident(ident) if ident == RustString => { @@ -189,6 +207,7 @@ fn expand_cxx_function_shim(namespace: &Namespace, efn: &ExternFn, types: &Types _ => quote!(#var), } }); + let vars = receiver_var.chain(arg_vars); let trampolines = efn .args .iter() @@ -274,18 +293,36 @@ fn expand_cxx_function_shim(namespace: &Namespace, efn: &ExternFn, types: &Types }) } .unwrap_or(call); - quote! { - #doc - pub fn #ident(#args) #ret { - extern "C" { - #decl + let receiver_ident = efn.receiver.as_ref().map(|base| &base.ident); + match receiver_ident { + None => quote! { + #doc + pub fn #ident(#(#all_args,)*) #ret { + extern "C" { + #decl + } + #trampolines + unsafe { + #setup + #expr + } } - #trampolines - unsafe { - #setup - #expr + }, + Some(base_ident) => quote! { + #doc + impl #base_ident { + pub fn #ident(#(#all_args,)*) #ret { + extern "C" { + #decl + } + #trampolines + unsafe { + #setup + #expr + } + } } - } + }, } } @@ -333,7 +370,11 @@ fn expand_rust_type(ety: &ExternType) -> TokenStream { fn expand_rust_function_shim(namespace: &Namespace, efn: &ExternFn, types: &Types) -> TokenStream { let ident = &efn.ident; - let link_name = format!("{}cxxbridge02${}", namespace, ident); + let receiver_type = match &efn.receiver { + Some(base) => base.ident.to_string(), + None => "_".to_string(), + }; + let link_name = format!("{}cxxbridge02${}${}", namespace, receiver_type, ident); let local_name = format_ident!("__{}", ident); let catch_unwind_label = format!("::{}", ident); let invoke = Some(ident); @@ -355,6 +396,13 @@ fn expand_rust_function_shim_impl( catch_unwind_label: String, invoke: Option<&Ident>, ) -> TokenStream { + let receiver = sig.receiver.iter().map(|base| { + let ident = &base.ident; + match base.mutability { + None => quote!(__receiver: &#ident), + Some(_) => quote!(__receiver: &mut #ident), + } + }); let args = sig.args.iter().map(|arg| { let ident = &arg.ident; let ty = expand_extern_type(&arg.ty); @@ -364,6 +412,7 @@ fn expand_rust_function_shim_impl( quote!(#ident: #ty) } }); + let all_args = receiver.chain(args); let vars = sig.args.iter().map(|arg| { let ident = &arg.ident; @@ -385,7 +434,10 @@ fn expand_rust_function_shim_impl( }); let mut call = match invoke { - Some(ident) => quote!(super::#ident), + Some(ident) => match sig.receiver { + None => quote!(super::#ident), + Some(_) => quote!(__receiver.#ident), + }, None => quote!(__extern), }; call.extend(quote! { (#(#vars),*) }); @@ -443,7 +495,7 @@ fn expand_rust_function_shim_impl( quote! { #[doc(hidden)] #[export_name = #link_name] - unsafe extern "C" fn #local_name(#(#args,)* #outparam #pointer) #ret { + unsafe extern "C" fn #local_name(#(#all_args,)* #outparam #pointer) #ret { let __fn = concat!(module_path!(), #catch_unwind_label); #expr } diff --git a/src/lib.rs b/src/lib.rs index 609db59e2..780a26e6b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,8 +79,10 @@ //! //! // Functions implemented in C++. //! fn make_demo(appname: &str) -> UniquePtr; -//! fn get_name(thing: &ThingC) -> &CxxString; //! fn do_thing(state: SharedThing); +//! +//! // Methods implemented in C++. +//! fn get_name(self: &ThingC) -> &CxxString; //! } //! //! extern "Rust" { @@ -90,6 +92,9 @@ //! //! // Functions implemented in Rust. //! fn print_r(r: &ThingR); +//! +//! // Methods implemented in Rust. +//! fn print(self: &ThingR); //! } //! } //! # @@ -99,6 +104,12 @@ //! # println!("called back with r={}", r.0); //! # } //! # +//! # impl ThingR { +//! # fn print(&self) { +//! # println!("method called back with r={}", self.0); +//! # } +//! # } +//! # //! # fn main() {} //! ``` //! diff --git a/syntax/check.rs b/syntax/check.rs index 181c5302b..3b6937aaa 100644 --- a/syntax/check.rs +++ b/syntax/check.rs @@ -195,6 +195,10 @@ fn check_multiple_arg_lifetimes(cx: &mut Check, efn: &ExternFn) { } } + if efn.receiver.is_some() { + reference_args += 1; + } + if reference_args != 1 { cx.error( efn, diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs index 8afa84106..4da57403c 100644 --- a/tests/ffi/lib.rs +++ b/tests/ffi/lib.rs @@ -49,10 +49,14 @@ pub mod ffi { fn c_try_return_sliceu8(s: &[u8]) -> Result<&[u8]>; fn c_try_return_rust_string() -> Result; fn c_try_return_unique_ptr_string() -> Result>; + + fn get(self: &C) -> usize; + fn set(self: &mut C, n: usize) -> usize; } extern "Rust" { type R; + type R2; fn r_return_primitive() -> usize; fn r_return_shared() -> Shared; @@ -77,11 +81,28 @@ pub mod ffi { fn r_try_return_void() -> Result<()>; fn r_try_return_primitive() -> Result; fn r_fail_return_primitive() -> Result; + + fn r_return_r2(n: usize) -> Box; + fn get(self: &R2) -> usize; + fn set(self: &mut R2, n: usize) -> usize; } } pub type R = usize; +pub struct R2(usize); + +impl R2 { + fn get(&self) -> usize { + self.0 + } + + fn set(&mut self, n: usize) -> usize { + self.0 = n; + n + } +} + #[derive(Debug)] struct Error; @@ -184,3 +205,7 @@ fn r_try_return_primitive() -> Result { fn r_fail_return_primitive() -> Result { Err(Error) } + +fn r_return_r2(n: usize) -> Box { + Box::new(R2(n)) +} diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc index d72bfd007..8893ee84a 100644 --- a/tests/ffi/tests.cc +++ b/tests/ffi/tests.cc @@ -15,6 +15,11 @@ C::C(size_t n) : n(n) {} size_t C::get() const { return this->n; } +size_t C::set(size_t n) { + this->n = n; + return this->n; +} + size_t c_return_primitive() { return 2020; } Shared c_return_shared() { return Shared{2020}; } @@ -176,6 +181,13 @@ extern "C" const char *cxx_run_test() noexcept { ASSERT(std::strcmp(e.what(), "rust error") == 0); } + auto r2 = r_return_r2(2020); + ASSERT(r2->get() == 2020); + ASSERT(r2->set(2021) == 2021); + ASSERT(r2->get() == 2021); + ASSERT(r2->set(2020) == 2020); + ASSERT(r2->get() == 2020); + cxx_test_suite_set_correct(); return nullptr; } diff --git a/tests/ffi/tests.h b/tests/ffi/tests.h index a68be5cce..43ac229fb 100644 --- a/tests/ffi/tests.h +++ b/tests/ffi/tests.h @@ -12,6 +12,7 @@ class C { public: C(size_t n); size_t get() const; + size_t set(size_t n); private: size_t n; diff --git a/tests/test.rs b/tests/test.rs index 511c52bfc..36de8f91d 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -107,6 +107,18 @@ fn test_c_call_r() { check!(cxx_run_test()); } +#[test] +fn test_c_method_calls() { + let mut unique_ptr = ffi::c_return_unique_ptr(); + + let old_value = unique_ptr.as_ref().unwrap().get(); + assert_eq!(2020, old_value); + assert_eq!(2021, unique_ptr.as_mut().unwrap().set(2021)); + assert_eq!(2021, unique_ptr.as_ref().unwrap().get()); + assert_eq!(old_value, unique_ptr.as_mut().unwrap().set(old_value)); + assert_eq!(old_value, unique_ptr.as_ref().unwrap().get()) +} + #[no_mangle] extern "C" fn cxx_test_suite_get_box() -> *mut cxx_test_suite::R { Box::into_raw(Box::new(2020usize))