Skip to content

Commit af74d66

Browse files
authored
Merge pull request #121 from jgalenson/methods
Support calling C++ and Rust methods
2 parents 4272d98 + 187588e commit af74d66

File tree

13 files changed

+271
-42
lines changed

13 files changed

+271
-42
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ anyhow = "1.0"
1919
cc = "1.0.49"
2020
codespan-reporting = "0.9"
2121
cxxbridge-macro = { version = "=0.2.9", path = "macro" }
22+
itertools = "0.9"
2223
link-cplusplus = "1.0"
2324
proc-macro2 = { version = "1.0", features = ["span-locations"] }
2425
quote = "1.0"

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,10 @@ mod ffi {
8484

8585
// Functions implemented in C++.
8686
fn make_demo(appname: &str) -> UniquePtr<ThingC>;
87-
fn get_name(thing: &ThingC) -> &CxxString;
8887
fn do_thing(state: SharedThing);
88+
89+
// Methods implemented in C++.
90+
fn get_name(self: &ThingC) -> &CxxString;
8991
}
9092

9193
extern "Rust" {
@@ -95,6 +97,9 @@ mod ffi {
9597

9698
// Functions implemented in Rust.
9799
fn print_r(r: &ThingR);
100+
101+
// Methods implemented in Rust.
102+
fn print(self: &ThingR);
98103
}
99104
}
100105
```
@@ -335,8 +340,6 @@ This is still early days for CXX; I am releasing it as a minimum viable product
335340
to collect feedback on the direction and invite collaborators. Here are some of
336341
the facets that I still intend for this project to tackle:
337342

338-
- [ ] Support associated methods: `extern "Rust" { fn f(self: &Struct); }`
339-
- [ ] Support C++ member functions
340343
- [ ] Support structs with type parameters
341344
- [ ] Support async functions
342345

demo-cxx/demo.cc

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@ ThingC::ThingC(std::string appname) : appname(std::move(appname)) {}
99

1010
ThingC::~ThingC() { std::cout << "done with ThingC" << std::endl; }
1111

12+
const std::string &ThingC::get_name() const {
13+
std::cout << "I'm a C++ method!" << std::endl;
14+
return this->appname;
15+
}
16+
1217
std::unique_ptr<ThingC> make_demo(rust::Str appname) {
1318
return std::unique_ptr<ThingC>(new ThingC(std::string(appname)));
1419
}
1520

16-
const std::string &get_name(const ThingC &thing) { return thing.appname; }
17-
18-
void do_thing(SharedThing state) { print_r(*state.y); }
21+
void do_thing(SharedThing state) {
22+
print_r(*state.y);
23+
state.y->print();
24+
}
1925

2026
} // namespace example
2127
} // namespace org

demo-cxx/demo.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ class ThingC {
1212
~ThingC();
1313

1414
std::string appname;
15+
16+
const std::string &get_name() const;
1517
};
1618

1719
struct SharedThing;
1820

1921
std::unique_ptr<ThingC> make_demo(rust::Str appname);
20-
const std::string &get_name(const ThingC &thing);
2122
void do_thing(SharedThing state);
2223

2324
} // namespace example

demo-rs/src/main.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ mod ffi {
1111

1212
type ThingC;
1313
fn make_demo(appname: &str) -> UniquePtr<ThingC>;
14-
fn get_name(thing: &ThingC) -> &CxxString;
14+
fn get_name(self: &ThingC) -> &CxxString;
1515
fn do_thing(state: SharedThing);
16+
1617
}
1718

1819
extern "Rust" {
1920
type ThingR;
2021
fn print_r(r: &ThingR);
22+
fn print(self: &ThingR);
2123
}
2224
}
2325

@@ -27,9 +29,15 @@ fn print_r(r: &ThingR) {
2729
println!("called back with r={}", r.0);
2830
}
2931

32+
impl ThingR {
33+
fn print(&self) {
34+
println!("method called back with r={}", self.0);
35+
}
36+
}
37+
3038
fn main() {
3139
let x = ffi::make_demo("demo of cxx::bridge");
32-
println!("this is a {}", ffi::get_name(x.as_ref().unwrap()));
40+
println!("this is a {}", x.as_ref().unwrap().get_name());
3341

3442
ffi::do_thing(ffi::SharedThing {
3543
z: 222,

gen/write.rs

Lines changed: 108 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ use crate::gen::namespace::Namespace;
22
use crate::gen::out::OutFile;
33
use crate::gen::{include, Opt};
44
use crate::syntax::atom::Atom::{self, *};
5-
use crate::syntax::{Api, ExternFn, Signature, Struct, Type, Types, Var};
5+
use crate::syntax::{Api, ExternFn, ExternType, Receiver, Signature, Struct, Type, Types, Var};
6+
use itertools::Itertools;
67
use proc_macro2::Ident;
78

89
pub(super) fn gen(
@@ -44,10 +45,30 @@ pub(super) fn gen(
4445
}
4546
}
4647

48+
let methods_for_type = apis.iter().filter_map(|api| match api {
49+
Api::RustFunction(efn) => match &efn.sig.receiver {
50+
Some(rcvr) => Some((&rcvr.ident, efn)),
51+
_ => None,
52+
},
53+
_ => None,
54+
}).into_group_map();
55+
4756
for api in apis {
48-
if let Api::Struct(strct) = api {
49-
out.next_section();
50-
write_struct(out, strct);
57+
match api {
58+
Api::Struct(strct) => {
59+
out.next_section();
60+
write_struct(out, strct);
61+
}
62+
Api::RustType(ety) => {
63+
match methods_for_type.get(&ety.ident) {
64+
Some(methods) => {
65+
out.next_section();
66+
write_struct_with_methods(out, ety, methods);
67+
},
68+
_ => {}
69+
}
70+
}
71+
_ => {}
5172
}
5273
}
5374

@@ -300,6 +321,23 @@ fn write_struct_using(out: &mut OutFile, ident: &Ident) {
300321
writeln!(out, "using {} = {};", ident, ident);
301322
}
302323

324+
fn write_struct_with_methods(out: &mut OutFile, ety: &ExternType, methods: &Vec<&ExternFn>) {
325+
for line in ety.doc.to_string().lines() {
326+
writeln!(out, "//{}", line);
327+
}
328+
writeln!(out, "struct {} final {{", ety.ident);
329+
writeln!(out, " {}() = delete;", ety.ident);
330+
writeln!(out, " {}(const {}&) = delete;", ety.ident, ety.ident);
331+
for method in methods {
332+
write!(out, " ");
333+
let sig = &method.sig;
334+
let local_name = method.ident.to_string();
335+
write_rust_function_shim_decl(out, &local_name, sig, None, false);
336+
writeln!(out, ";");
337+
}
338+
writeln!(out, "}};");
339+
}
340+
303341
fn write_exception_glue(out: &mut OutFile, apis: &[Api]) {
304342
let mut has_cxx_throws = false;
305343
for api in apis {
@@ -326,9 +364,16 @@ fn write_cxx_function_shim(out: &mut OutFile, efn: &ExternFn, types: &Types) {
326364
} else {
327365
write_extern_return_type_space(out, &efn.ret, types);
328366
}
329-
write!(out, "{}cxxbridge02${}(", out.namespace, efn.ident);
367+
let receiver_type = match &efn.receiver {
368+
Some(base) => base.ident.to_string(),
369+
None => "_".to_string(),
370+
};
371+
write!(out, "{}cxxbridge02${}${}(", out.namespace, receiver_type, efn.ident);
372+
if let Some(base) = &efn.receiver {
373+
write!(out, "{} *__receiver$", base.ident);
374+
}
330375
for (i, arg) in efn.args.iter().enumerate() {
331-
if i > 0 {
376+
if i > 0 || efn.receiver.is_some() {
332377
write!(out, ", ");
333378
}
334379
if arg.ty == RustString {
@@ -347,14 +392,27 @@ fn write_cxx_function_shim(out: &mut OutFile, efn: &ExternFn, types: &Types) {
347392
writeln!(out, ") noexcept {{");
348393
write!(out, " ");
349394
write_return_type(out, &efn.ret);
350-
write!(out, "(*{}$)(", efn.ident);
395+
match &efn.receiver {
396+
None => write!(out, "(*{}$)(", efn.ident),
397+
Some(base) => write!(out, "({}::*{}$)(", base.ident, efn.ident),
398+
}
351399
for (i, arg) in efn.args.iter().enumerate() {
352400
if i > 0 {
353401
write!(out, ", ");
354402
}
355403
write_type(out, &arg.ty);
356404
}
357-
writeln!(out, ") = {};", efn.ident);
405+
write!(out, ")");
406+
match &efn.receiver {
407+
Some(Receiver { mutability: None, ident: _ }) => write!(out, " const"),
408+
_ => {},
409+
}
410+
write!(out, " = ");
411+
match &efn.receiver {
412+
None => write!(out, "{}", efn.ident),
413+
Some(base) => write!(out, "&{}::{}", base.ident, efn.ident),
414+
}
415+
writeln!(out, ";");
358416
write!(out, " ");
359417
if efn.throws {
360418
writeln!(out, "::rust::Str::Repr throw$;");
@@ -377,7 +435,10 @@ fn write_cxx_function_shim(out: &mut OutFile, efn: &ExternFn, types: &Types) {
377435
}
378436
_ => {}
379437
}
380-
write!(out, "{}$(", efn.ident);
438+
match &efn.receiver {
439+
None => write!(out, "{}$(", efn.ident),
440+
Some(_) => write!(out, "(__receiver$->*{}$)(", efn.ident),
441+
}
381442
for (i, arg) in efn.args.iter().enumerate() {
382443
if i > 0 {
383444
write!(out, ", ");
@@ -452,7 +513,11 @@ fn write_function_pointer_trampoline(
452513
}
453514

454515
fn write_rust_function_decl(out: &mut OutFile, efn: &ExternFn, types: &Types) {
455-
let link_name = format!("{}cxxbridge02${}", out.namespace, efn.ident);
516+
let receiver_type = match &efn.receiver {
517+
Some(base) => base.ident.to_string(),
518+
None => "_".to_string(),
519+
};
520+
let link_name = format!("{}cxxbridge02${}${}", out.namespace, receiver_type, efn.ident);
456521
let indirect_call = false;
457522
write_rust_function_decl_impl(out, &link_name, efn, types, indirect_call);
458523
}
@@ -471,6 +536,10 @@ fn write_rust_function_decl_impl(
471536
}
472537
write!(out, "{}(", link_name);
473538
let mut needs_comma = false;
539+
if let Some(base) = &sig.receiver {
540+
write!(out, "{} &__receiver$", base.ident);
541+
needs_comma = true;
542+
}
474543
for arg in &sig.args {
475544
if needs_comma {
476545
write!(out, ", ");
@@ -500,20 +569,26 @@ fn write_rust_function_shim(out: &mut OutFile, efn: &ExternFn, types: &Types) {
500569
writeln!(out, "//{}", line);
501570
}
502571
let local_name = efn.ident.to_string();
503-
let invoke = format!("{}cxxbridge02${}", out.namespace, efn.ident);
572+
let receiver_type = match &efn.receiver {
573+
Some(base) => base.ident.to_string(),
574+
None => "_".to_string(),
575+
};
576+
let invoke = format!("{}cxxbridge02${}${}", out.namespace, receiver_type, efn.ident);
504577
let indirect_call = false;
505578
write_rust_function_shim_impl(out, &local_name, efn, types, &invoke, indirect_call);
506579
}
507580

508-
fn write_rust_function_shim_impl(
581+
fn write_rust_function_shim_decl(
509582
out: &mut OutFile,
510583
local_name: &str,
511584
sig: &Signature,
512-
types: &Types,
513-
invoke: &str,
585+
receiver: Option<&Receiver>,
514586
indirect_call: bool,
515587
) {
516588
write_return_type(out, &sig.ret);
589+
if let Some(base) = receiver {
590+
write!(out, "{}::", base.ident);
591+
}
517592
write!(out, "{}(", local_name);
518593
for (i, arg) in sig.args.iter().enumerate() {
519594
if i > 0 {
@@ -532,6 +607,21 @@ fn write_rust_function_shim_impl(
532607
if !sig.throws {
533608
write!(out, " noexcept");
534609
}
610+
}
611+
612+
fn write_rust_function_shim_impl(
613+
out: &mut OutFile,
614+
local_name: &str,
615+
sig: &Signature,
616+
types: &Types,
617+
invoke: &str,
618+
indirect_call: bool,
619+
) {
620+
if out.header && sig.receiver.is_some() {
621+
// We've already defined this inside the struct.
622+
return;
623+
}
624+
write_rust_function_shim_decl(out, local_name, sig, sig.receiver.as_ref(), indirect_call);
535625
if out.header {
536626
writeln!(out, ";");
537627
} else {
@@ -570,8 +660,11 @@ fn write_rust_function_shim_impl(
570660
write!(out, "::rust::Str::Repr error$ = ");
571661
}
572662
write!(out, "{}(", invoke);
663+
if let Some(_) = &sig.receiver {
664+
write!(out, "*this");
665+
}
573666
for (i, arg) in sig.args.iter().enumerate() {
574-
if i > 0 {
667+
if i > 0 || sig.receiver.is_some() {
575668
write!(out, ", ");
576669
}
577670
match &arg.ty {

0 commit comments

Comments
 (0)