From 3d4f612b348607bb19d0f2013e383485eb18bb2c Mon Sep 17 00:00:00 2001 From: Joel Galenson Date: Tue, 7 Apr 2020 15:54:05 -0700 Subject: [PATCH 1/5] Support calling C++ methods from Rust These methods can be declared in the bridge by naming the first argument self and making it a reference to the containing class, e.g., fn get(self: &C) -> usize; fn set(self: &mut C, n: usize); This syntax requires Rust 1.43. Note that the implementation also changes the internal naming of shim functions so that they also contain the name of the owning class, if any. This allows defining multiple methods with the same name on different objects. --- demo-cxx/demo.cc | 7 ++++-- demo-cxx/demo.h | 3 ++- demo-rs/src/main.rs | 5 ++-- gen/write.rs | 29 ++++++++++++++++++---- macro/src/expand.rs | 59 +++++++++++++++++++++++++++++++++++---------- syntax/check.rs | 4 +++ tests/ffi/lib.rs | 3 +++ tests/ffi/tests.cc | 5 ++++ tests/ffi/tests.h | 1 + tests/test.rs | 12 +++++++++ 10 files changed, 105 insertions(+), 23 deletions(-) diff --git a/demo-cxx/demo.cc b/demo-cxx/demo.cc index cd447ea0e..6fc246589 100644 --- a/demo-cxx/demo.cc +++ b/demo-cxx/demo.cc @@ -9,12 +9,15 @@ 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); } } // namespace example 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..759bbe179 100644 --- a/demo-rs/src/main.rs +++ b/demo-rs/src/main.rs @@ -11,8 +11,9 @@ 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" { @@ -29,7 +30,7 @@ fn print_r(r: &ThingR) { 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..41b5b3743 100644 --- a/gen/write.rs +++ b/gen/write.rs @@ -2,7 +2,7 @@ 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, Receiver, Signature, Struct, Type, Types, Var}; use proc_macro2::Ident; pub(super) fn gen( @@ -327,8 +327,11 @@ fn write_cxx_function_shim(out: &mut OutFile, efn: &ExternFn, types: &Types) { write_extern_return_type_space(out, &efn.ret, types); } write!(out, "{}cxxbridge02${}(", out.namespace, 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 +350,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 +393,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, ", "); diff --git a/macro/src/expand.rs b/macro/src/expand.rs index 146d21b10..998b47e85 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 { @@ -150,7 +158,7 @@ fn expand_cxx_function_decl(namespace: &Namespace, efn: &ExternFn, types: &Types 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 +166,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 +182,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 +203,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 +289,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 + } + } } - } + }, } } 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..a720a80d2 100644 --- a/tests/ffi/lib.rs +++ b/tests/ffi/lib.rs @@ -49,6 +49,9 @@ 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" { diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc index d72bfd007..2daae5333 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}; } 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)) From c1c4e7ac6b92ad33f134882f848a6ad034ef3de2 Mon Sep 17 00:00:00 2001 From: Joel Galenson Date: Wed, 15 Apr 2020 10:21:00 -0700 Subject: [PATCH 2/5] Support calling Rust methods from C++ These methods can be declared in the bridge by naming the first argument self and making it a reference to the containing class, e.g., fn get(self: &R) -> usize; fn set(self: &mut R, n: usize); This syntax requires Rust 1.43. --- demo-cxx/demo.cc | 5 ++- demo-rs/src/main.rs | 7 ++++ gen/write.rs | 89 +++++++++++++++++++++++++++++++++++++++------ macro/src/expand.rs | 27 ++++++++++++-- tests/ffi/lib.rs | 22 +++++++++++ tests/ffi/tests.cc | 7 ++++ 6 files changed, 141 insertions(+), 16 deletions(-) diff --git a/demo-cxx/demo.cc b/demo-cxx/demo.cc index 6fc246589..bc0d97685 100644 --- a/demo-cxx/demo.cc +++ b/demo-cxx/demo.cc @@ -18,7 +18,10 @@ std::unique_ptr make_demo(rust::Str appname) { return std::unique_ptr(new ThingC(std::string(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-rs/src/main.rs b/demo-rs/src/main.rs index 759bbe179..713a1d1a7 100644 --- a/demo-rs/src/main.rs +++ b/demo-rs/src/main.rs @@ -19,6 +19,7 @@ mod ffi { extern "Rust" { type ThingR; fn print_r(r: &ThingR); + fn print(self: &ThingR); } } @@ -28,6 +29,12 @@ 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 {}", x.as_ref().unwrap().get_name()); diff --git a/gen/write.rs b/gen/write.rs index 41b5b3743..7e1caac47 100644 --- a/gen/write.rs +++ b/gen/write.rs @@ -2,7 +2,7 @@ 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, Receiver, Signature, Struct, Type, Types, Var}; +use crate::syntax::{Api, ExternFn, ExternType, Receiver, Signature, Struct, Type, Types, Var}; use proc_macro2::Ident; pub(super) fn gen( @@ -45,9 +45,25 @@ pub(super) fn gen( } 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) => { + let methods = apis.iter().filter_map(|api| match api { + Api::RustFunction(efn) => match &efn.sig.receiver { + Some(rcvr) if rcvr.ident == ety.ident => Some(efn), + _ => None, + }, + _ => None, + }).collect::>(); + if !methods.is_empty() { + out.next_section(); + write_struct_with_methods(out, ety, methods); + } + } + _ => {} } } @@ -300,6 +316,21 @@ 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); + 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,7 +357,11 @@ 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); } @@ -471,7 +506,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); } @@ -490,6 +529,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, ", "); @@ -519,20 +562,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 { @@ -551,6 +600,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 { @@ -589,8 +653,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 998b47e85..2d3166a20 100644 --- a/macro/src/expand.rs +++ b/macro/src/expand.rs @@ -154,7 +154,11 @@ 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] @@ -366,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); @@ -388,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); @@ -397,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; @@ -418,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),*) }); @@ -476,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/tests/ffi/lib.rs b/tests/ffi/lib.rs index a720a80d2..4da57403c 100644 --- a/tests/ffi/lib.rs +++ b/tests/ffi/lib.rs @@ -56,6 +56,7 @@ pub mod ffi { extern "Rust" { type R; + type R2; fn r_return_primitive() -> usize; fn r_return_shared() -> Shared; @@ -80,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; @@ -187,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 2daae5333..8893ee84a 100644 --- a/tests/ffi/tests.cc +++ b/tests/ffi/tests.cc @@ -181,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; } From 968738f12741b34fd9b6b4e8888bd576ca46cd92 Mon Sep 17 00:00:00 2001 From: Joel Galenson Date: Wed, 15 Apr 2020 14:19:33 -0700 Subject: [PATCH 3/5] Optimize the computation of the methods of a struct --- Cargo.toml | 1 + gen/write.rs | 27 ++++++++++++++++----------- 2 files changed, 17 insertions(+), 11 deletions(-) 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/gen/write.rs b/gen/write.rs index 7e1caac47..21fbc8ce3 100644 --- a/gen/write.rs +++ b/gen/write.rs @@ -3,6 +3,7 @@ use crate::gen::out::OutFile; use crate::gen::{include, Opt}; use crate::syntax::atom::Atom::{self, *}; use crate::syntax::{Api, ExternFn, ExternType, Receiver, Signature, Struct, Type, Types, Var}; +use itertools::Itertools; use proc_macro2::Ident; pub(super) fn gen( @@ -44,6 +45,14 @@ 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 { match api { Api::Struct(strct) => { @@ -51,16 +60,12 @@ pub(super) fn gen( write_struct(out, strct); } Api::RustType(ety) => { - let methods = apis.iter().filter_map(|api| match api { - Api::RustFunction(efn) => match &efn.sig.receiver { - Some(rcvr) if rcvr.ident == ety.ident => Some(efn), - _ => None, + match methods_for_type.get(&ety.ident) { + Some(methods) => { + out.next_section(); + write_struct_with_methods(out, ety, methods); }, - _ => None, - }).collect::>(); - if !methods.is_empty() { - out.next_section(); - write_struct_with_methods(out, ety, methods); + _ => {} } } _ => {} @@ -316,12 +321,12 @@ 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>) { +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); - for method in &methods { + for method in methods { write!(out, " "); let sig = &method.sig; let local_name = method.ident.to_string(); From f937996fc7c3b4ff9694c1afb36ea0901284e65c Mon Sep 17 00:00:00 2001 From: Joel Galenson Date: Thu, 16 Apr 2020 14:11:25 -0700 Subject: [PATCH 4/5] Update documentation --- README.md | 9 ++++++--- src/lib.rs | 13 ++++++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) 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/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() {} //! ``` //! From 187588eeacea37574d98e892d824776dce86867b Mon Sep 17 00:00:00 2001 From: Joel Galenson Date: Fri, 17 Apr 2020 16:19:54 -0700 Subject: [PATCH 5/5] Mark default and copy constructors as deleted. --- gen/write.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gen/write.rs b/gen/write.rs index 21fbc8ce3..b8d17dfd2 100644 --- a/gen/write.rs +++ b/gen/write.rs @@ -326,6 +326,8 @@ fn write_struct_with_methods(out: &mut OutFile, ety: &ExternType, methods: &Vec< 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;