Repository: dtolnay/cxx
Pull request #518 (2020-11-27 23:47:26)

Emit operator== for shared structs with derive(PartialEq)
Part of #109.

```rust
#[cxx::bridge]
mod ffi {
    #[derive(PartialEq)]
    struct Struct {
        x: i32,
    }
}
```

```cpp
// generated

struct Struct final {
  int32_t x;

  bool operator==(const Struct &) const noexcept;
  bool operator!=(const Struct &) const noexcept;
};
```


Patch:
diff --git a/gen/src/write.rs b/gen/src/write.rs
index 60936a0b..54c0bba0 100644
--- a/gen/src/write.rs
+++ b/gen/src/write.rs
@@ -5,8 +5,8 @@ use crate::gen::{builtin, include, Opt};
 use crate::syntax::atom::Atom::{self, *};
 use crate::syntax::symbol::Symbol;
 use crate::syntax::{
-    mangle, Api, Enum, ExternFn, ExternType, Pair, ResolvableName, Signature, Struct, Type, Types,
-    Var,
+    derive, mangle, Api, Enum, ExternFn, ExternType, Pair, ResolvableName, Signature, Struct,
+    Trait, Type, Types, Var,
 };
 use proc_macro2::Ident;
 use std::collections::{HashMap, HashSet};
@@ -128,6 +128,7 @@ fn write_functions<'a>(out: &mut OutFile<'a>, apis: &'a [Api]) {
     if !out.header {
         for api in apis {
             match api {
+                Api::Struct(strct) => write_struct_operator_decls(out, strct),
                 Api::CxxFunction(efn) => write_cxx_function_shim(out, efn),
                 Api::RustFunction(efn) => write_rust_function_decl(out, efn),
                 _ => {}
@@ -136,9 +137,13 @@ fn write_functions<'a>(out: &mut OutFile<'a>, apis: &'a [Api]) {
     }
 
     for api in apis {
-        if let Api::RustFunction(efn) = api {
-            out.next_section();
-            write_rust_function_shim(out, efn);
+        match api {
+            Api::Struct(strct) => write_struct_operators(out, strct),
+            Api::RustFunction(efn) => {
+                out.next_section();
+                write_rust_function_shim(out, efn);
+            }
+            _ => {}
         }
     }
 }
@@ -175,6 +180,8 @@ fn pick_includes_and_builtins(out: &mut OutFile, apis: &[Api]) {
 }
 
 fn write_struct<'a>(out: &mut OutFile<'a>, strct: &'a Struct, methods: &[&ExternFn]) {
+    let operator_eq = derive::contains(&strct.derives, Trait::PartialEq);
+
     out.set_namespace(&strct.name.namespace);
     let guard = format!("CXXBRIDGE1_STRUCT_{}", strct.name.to_symbol());
     writeln!(out, "#ifndef {}", guard);
@@ -188,7 +195,7 @@ fn write_struct<'a>(out: &mut OutFile<'a>, strct: &'a Struct, methods: &[&Extern
         write_type_space(out, &field.ty);
         writeln!(out, "{};", field.ident);
     }
-    if !methods.is_empty() {
+    if !methods.is_empty() || operator_eq {
         writeln!(out);
     }
     for method in methods {
@@ -198,6 +205,18 @@ fn write_struct<'a>(out: &mut OutFile<'a>, strct: &'a Struct, methods: &[&Extern
         write_rust_function_shim_decl(out, &local_name, sig, false);
         writeln!(out, ";");
     }
+    if operator_eq {
+        writeln!(
+            out,
+            "  bool operator==(const {} &) const noexcept;",
+            strct.name.cxx,
+        );
+        writeln!(
+            out,
+            "  bool operator!=(const {} &) const noexcept;",
+            strct.name.cxx,
+        );
+    }
     writeln!(out, "}};");
     writeln!(out, "#endif // {}", guard);
 }
@@ -330,6 +349,50 @@ fn check_trivial_extern_type(out: &mut OutFile, id: &Pair) {
     );
 }
 
+fn write_struct_operator_decls<'a>(out: &mut OutFile<'a>, strct: &'a Struct) {
+    out.set_namespace(&strct.name.namespace);
+    out.begin_block(Block::ExternC);
+
+    if derive::contains(&strct.derives, Trait::PartialEq) {
+        let link_name = mangle::operator(&strct.name, "__operator_eq");
+        writeln!(
+            out,
+            "bool {}(const {1} &, const {1} &) noexcept;",
+            link_name, strct.name.cxx,
+        );
+    }
+
+    out.end_block(Block::ExternC);
+}
+
+fn write_struct_operators<'a>(out: &mut OutFile<'a>, strct: &'a Struct) {
+    if out.header {
+        return;
+    }
+
+    out.set_namespace(&strct.name.namespace);
+
+    if derive::contains(&strct.derives, Trait::PartialEq) {
+        let link_name = mangle::operator(&strct.name, "__operator_eq");
+        out.next_section();
+        writeln!(
+            out,
+            "bool {0}::operator==(const {0} &rhs) const noexcept {{",
+            strct.name.cxx,
+        );
+        writeln!(out, "  return {}(*this, rhs);", link_name);
+        writeln!(out, "}}");
+        out.next_section();
+        writeln!(
+            out,
+            "bool {0}::operator!=(const {0} &rhs) const noexcept {{",
+            strct.name.cxx,
+        );
+        writeln!(out, "  return !(*this == rhs);");
+        writeln!(out, "}}");
+    }
+}
+
 fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) {
     out.next_section();
     out.set_namespace(&efn.name.namespace);
diff --git a/macro/src/derive.rs b/macro/src/derive.rs
index 2109a1f6..28dfae3d 100644
--- a/macro/src/derive.rs
+++ b/macro/src/derive.rs
@@ -2,8 +2,11 @@ use crate::syntax::{derive, Enum, Struct, Trait};
 use proc_macro2::{Ident, Span, TokenStream};
 use quote::{quote, quote_spanned, ToTokens};
 
-pub fn expand_struct(strct: &Struct) -> TokenStream {
+pub use crate::syntax::derive::*;
+
+pub fn expand_struct(strct: &Struct, actual_derives: &mut Option<TokenStream>) -> TokenStream {
     let mut expanded = TokenStream::new();
+    let mut traits = Vec::new();
 
     for derive in &strct.derives {
         let span = derive.span;
@@ -11,16 +14,27 @@ pub fn expand_struct(strct: &Struct) -> TokenStream {
             Trait::Copy => expanded.extend(struct_copy(strct, span)),
             Trait::Clone => expanded.extend(struct_clone(strct, span)),
             Trait::Debug => expanded.extend(struct_debug(strct, span)),
+            Trait::Eq => traits.push(quote_spanned!(span=> ::std::cmp::Eq)),
+            Trait::PartialEq => traits.push(quote_spanned!(span=> ::std::cmp::PartialEq)),
         }
     }
 
+    if traits.is_empty() {
+        *actual_derives = None;
+    } else {
+        *actual_derives = Some(quote!(#[derive(#(#traits),*)]));
+    }
+
     expanded
 }
 
-pub fn expand_enum(enm: &Enum) -> TokenStream {
+pub fn expand_enum(enm: &Enum, actual_derives: &mut Option<TokenStream>) -> TokenStream {
     let mut expanded = TokenStream::new();
+    let mut traits = Vec::new();
     let mut has_copy = false;
     let mut has_clone = false;
+    let mut has_eq = false;
+    let mut has_partial_eq = false;
 
     for derive in &enm.derives {
         let span = derive.span;
@@ -34,6 +48,14 @@ pub fn expand_enum(enm: &Enum) -> TokenStream {
                 has_clone = true;
             }
             Trait::Debug => expanded.extend(enum_debug(enm, span)),
+            Trait::Eq => {
+                traits.push(quote_spanned!(span=> ::std::cmp::Eq));
+                has_eq = true;
+            }
+            Trait::PartialEq => {
+                traits.push(quote_spanned!(span=> ::std::cmp::PartialEq));
+                has_partial_eq = true;
+            }
         }
     }
 
@@ -44,6 +66,16 @@ pub fn expand_enum(enm: &Enum) -> TokenStream {
     if !has_clone {
         expanded.extend(enum_clone(enm, span));
     }
+    if !has_eq {
+        // Required to be derived in order for the enum's "variants" to be
+        // usable in patterns.
+        traits.push(quote!(::std::cmp::Eq));
+    }
+    if !has_partial_eq {
+        traits.push(quote!(::std::cmp::PartialEq));
+    }
+
+    *actual_derives = Some(quote!(#[derive(#(#traits),*)]));
 
     expanded
 }
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index 29178208..bd1aefae 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -5,7 +5,7 @@ use crate::syntax::report::Errors;
 use crate::syntax::symbol::Symbol;
 use crate::syntax::{
     self, check, mangle, Api, Enum, ExternFn, ExternType, Impl, Pair, ResolvableName, Signature,
-    Struct, Type, TypeAlias, Types,
+    Struct, Trait, Type, TypeAlias, Types,
 };
 use proc_macro2::{Ident, Span, TokenStream};
 use quote::{format_ident, quote, quote_spanned, ToTokens};
@@ -40,7 +40,10 @@ fn expand(ffi: Module, apis: &[Api], types: &Types) -> TokenStream {
     for api in apis {
         match api {
             Api::Include(_) | Api::Impl(_) => {}
-            Api::Struct(strct) => expanded.extend(expand_struct(strct)),
+            Api::Struct(strct) => {
+                expanded.extend(expand_struct(strct));
+                hidden.extend(expand_struct_operators(strct));
+            }
             Api::Enum(enm) => expanded.extend(expand_enum(enm)),
             Api::CxxType(ety) => {
                 let ident = &ety.name.rust;
@@ -134,10 +137,12 @@ fn expand_struct(strct: &Struct) -> TokenStream {
         let vis = Token![pub](field.ident.span());
         quote!(#vis #field)
     });
-    let derived_traits = derive::expand_struct(strct);
+    let mut derives = None;
+    let derived_traits = derive::expand_struct(strct, &mut derives);
 
     quote! {
         #doc
+        #derives
         #[repr(C)]
         pub struct #ident {
             #(#fields,)*
@@ -152,6 +157,30 @@ fn expand_struct(strct: &Struct) -> TokenStream {
     }
 }
 
+fn expand_struct_operators(strct: &Struct) -> TokenStream {
+    let ident = &strct.name.rust;
+    let mut operators = TokenStream::new();
+
+    for derive in &strct.derives {
+        let span = derive.span;
+        match derive.what {
+            Trait::PartialEq => operators.extend({
+                let link_name = mangle::operator(&strct.name, "__operator_eq");
+                quote_spanned! {span=>
+                    #[doc(hidden)]
+                    #[export_name = #link_name]
+                    extern "C" fn __operator_eq(lhs: &#ident, rhs: &#ident) -> bool {
+                        *lhs == *rhs
+                    }
+                }
+            }),
+            _ => {}
+        }
+    }
+
+    operators
+}
+
 fn expand_enum(enm: &Enum) -> TokenStream {
     let ident = &enm.name.rust;
     let doc = &enm.doc;
@@ -164,12 +193,8 @@ fn expand_enum(enm: &Enum) -> TokenStream {
             pub const #variant_ident: Self = #ident { repr: #discriminant };
         })
     });
-    let derives = quote! {
-        // Required to be derived in order for the enum's "variants" to be
-        // usable in patterns.
-        #[derive(::std::cmp::PartialEq, ::std::cmp::Eq)]
-    };
-    let derived_traits = derive::expand_enum(enm);
+    let mut derives = None;
+    let derived_traits = derive::expand_enum(enm, &mut derives);
 
     quote! {
         #doc
diff --git a/syntax/derive.rs b/syntax/derive.rs
index 4522e686..d258cc7b 100644
--- a/syntax/derive.rs
+++ b/syntax/derive.rs
@@ -11,6 +11,8 @@ pub enum Trait {
     Clone,
     Copy,
     Debug,
+    Eq,
+    PartialEq,
 }
 
 impl Derive {
@@ -19,6 +21,8 @@ impl Derive {
             "Clone" => Trait::Clone,
             "Copy" => Trait::Copy,
             "Debug" => Trait::Debug,
+            "Eq" => Trait::Eq,
+            "PartialEq" => Trait::PartialEq,
             _ => return None,
         };
         let span = ident.span();
diff --git a/syntax/mangle.rs b/syntax/mangle.rs
index 480a0ff0..ef605eff 100644
--- a/syntax/mangle.rs
+++ b/syntax/mangle.rs
@@ -1,5 +1,5 @@
 use crate::syntax::symbol::{self, Symbol};
-use crate::syntax::{ExternFn, Types};
+use crate::syntax::{ExternFn, Pair, Types};
 use proc_macro2::Ident;
 
 const CXXBRIDGE: &str = "cxxbridge1";
@@ -25,6 +25,10 @@ pub fn extern_fn(efn: &ExternFn, types: &Types) -> Symbol {
     }
 }
 
+pub fn operator(receiver: &Pair, operator: &'static str) -> Symbol {
+    join!(receiver.namespace, CXXBRIDGE, receiver.cxx, operator)
+}
+
 // The C half of a function pointer trampoline.
 pub fn c_trampoline(efn: &ExternFn, var: &Ident, types: &Types) -> Symbol {
     join!(extern_fn(efn, types), var, 0)


Test patch:
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index 0d96038d..0ad753da 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -15,7 +15,7 @@ use std::os::raw::c_char;
 
 #[cxx::bridge(namespace = "tests")]
 pub mod ffi {
-    #[derive(Clone, Debug)]
+    #[derive(Clone, Debug, PartialEq, Eq)]
     struct Shared {
         z: usize,
     }
diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc
index 7cd84b81..340dc9c1 100644
--- a/tests/ffi/tests.cc
+++ b/tests/ffi/tests.cc
@@ -653,6 +653,9 @@ extern "C" const char *cxx_run_test() noexcept {
 
   ASSERT(std::string(rAliasedFunction(2020)) == "2020");
 
+  ASSERT(Shared{1} == Shared{1});
+  ASSERT(Shared{1} != Shared{2});
+
   cxx_test_suite_set_correct();
   return nullptr;
 }