Repository: dtolnay/cxx
Pull request #520 (2020-11-28 01:18:53)

Support derive(PartialOrd, Ord)
Part of #109. This adds `operator<`, `operator<=`, `operator>`, `operator>=` for shared structs that derive PartialOrd.


Patch:
diff --git a/gen/src/write.rs b/gen/src/write.rs
index 81eeef0c..2fd0f704 100644
--- a/gen/src/write.rs
+++ b/gen/src/write.rs
@@ -181,6 +181,7 @@ 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);
+    let operator_ord = derive::contains(&strct.derives, Trait::PartialOrd);
 
     out.set_namespace(&strct.name.namespace);
     let guard = format!("CXXBRIDGE1_STRUCT_{}", strct.name.to_symbol());
@@ -190,14 +191,17 @@ fn write_struct<'a>(out: &mut OutFile<'a>, strct: &'a Struct, methods: &[&Extern
         writeln!(out, "//{}", line);
     }
     writeln!(out, "struct {} final {{", strct.name.cxx);
+
     for field in &strct.fields {
         write!(out, "  ");
         write_type_space(out, &field.ty);
         writeln!(out, "{};", field.ident);
     }
-    if !methods.is_empty() || operator_eq {
+
+    if !methods.is_empty() || operator_eq || operator_ord {
         writeln!(out);
     }
+
     for method in methods {
         write!(out, "  ");
         let sig = &method.sig;
@@ -205,6 +209,7 @@ 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,
@@ -217,6 +222,30 @@ fn write_struct<'a>(out: &mut OutFile<'a>, strct: &'a Struct, methods: &[&Extern
             strct.name.cxx,
         );
     }
+
+    if operator_ord {
+        writeln!(
+            out,
+            "  bool operator<(const {} &) const noexcept;",
+            strct.name.cxx,
+        );
+        writeln!(
+            out,
+            "  bool operator<=(const {} &) const noexcept;",
+            strct.name.cxx,
+        );
+        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);
 }
@@ -371,6 +400,38 @@ fn write_struct_operator_decls<'a>(out: &mut OutFile<'a>, strct: &'a Struct) {
         }
     }
 
+    if derive::contains(&strct.derives, Trait::PartialOrd) {
+        let link_name = mangle::operator(&strct.name, "__operator_lt");
+        writeln!(
+            out,
+            "bool {}(const {1} &, const {1} &) noexcept;",
+            link_name, strct.name.cxx,
+        );
+
+        let link_name = mangle::operator(&strct.name, "__operator_le");
+        writeln!(
+            out,
+            "bool {}(const {1} &, const {1} &) noexcept;",
+            link_name, strct.name.cxx,
+        );
+
+        if !derive::contains(&strct.derives, Trait::Ord) {
+            let link_name = mangle::operator(&strct.name, "__operator_gt");
+            writeln!(
+                out,
+                "bool {}(const {1} &, const {1} &) noexcept;",
+                link_name, strct.name.cxx,
+            );
+
+            let link_name = mangle::operator(&strct.name, "__operator_ge");
+            writeln!(
+                out,
+                "bool {}(const {1} &, const {1} &) noexcept;",
+                link_name, strct.name.cxx,
+            );
+        }
+    }
+
     out.end_block(Block::ExternC);
 }
 
@@ -406,6 +467,56 @@ fn write_struct_operators<'a>(out: &mut OutFile<'a>, strct: &'a Struct) {
         }
         writeln!(out, "}}");
     }
+
+    if derive::contains(&strct.derives, Trait::PartialOrd) {
+        out.next_section();
+        writeln!(
+            out,
+            "bool {0}::operator<(const {0} &rhs) const noexcept {{",
+            strct.name.cxx,
+        );
+        let link_name = mangle::operator(&strct.name, "__operator_lt");
+        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,
+        );
+        let link_name = mangle::operator(&strct.name, "__operator_le");
+        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,
+        );
+        if derive::contains(&strct.derives, Trait::Ord) {
+            writeln!(out, "  return !(*this <= rhs);");
+        } else {
+            let link_name = mangle::operator(&strct.name, "__operator_gt");
+            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,
+        );
+        if derive::contains(&strct.derives, Trait::Ord) {
+            writeln!(out, "  return !(*this < rhs);");
+        } else {
+            let link_name = mangle::operator(&strct.name, "__operator_ge");
+            writeln!(out, "  return {}(*this, rhs);", link_name);
+        }
+        writeln!(out, "}}");
+    }
 }
 
 fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) {
diff --git a/macro/src/derive.rs b/macro/src/derive.rs
index 28dfae3d..91a5d9dc 100644
--- a/macro/src/derive.rs
+++ b/macro/src/derive.rs
@@ -15,7 +15,9 @@ pub fn expand_struct(strct: &Struct, actual_derives: &mut Option<TokenStream>) -
             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::Ord => expanded.extend(struct_ord(strct, span)),
             Trait::PartialEq => traits.push(quote_spanned!(span=> ::std::cmp::PartialEq)),
+            Trait::PartialOrd => expanded.extend(struct_partial_ord(strct, span)),
         }
     }
 
@@ -52,10 +54,12 @@ pub fn expand_enum(enm: &Enum, actual_derives: &mut Option<TokenStream>) -> Toke
                 traits.push(quote_spanned!(span=> ::std::cmp::Eq));
                 has_eq = true;
             }
+            Trait::Ord => expanded.extend(enum_ord(enm, span)),
             Trait::PartialEq => {
                 traits.push(quote_spanned!(span=> ::std::cmp::PartialEq));
                 has_partial_eq = true;
             }
+            Trait::PartialOrd => expanded.extend(enum_partial_ord(enm, span)),
         }
     }
 
@@ -132,6 +136,54 @@ fn struct_debug(strct: &Struct, span: Span) -> TokenStream {
     }
 }
 
+fn struct_ord(strct: &Struct, span: Span) -> TokenStream {
+    let ident = &strct.name.rust;
+    let fields = strct.fields.iter().map(|field| &field.ident);
+
+    quote_spanned! {span=>
+        impl ::std::cmp::Ord for #ident {
+            fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
+                #(
+                    match ::std::cmp::Ord::cmp(&self.#fields, &other.#fields) {
+                        ::std::cmp::Ordering::Equal => {}
+                        ordering => return ordering,
+                    }
+                )*
+                ::std::cmp::Ordering::Equal
+            }
+        }
+    }
+}
+
+fn struct_partial_ord(strct: &Struct, span: Span) -> TokenStream {
+    let ident = &strct.name.rust;
+
+    let body = if derive::contains(&strct.derives, Trait::Ord) {
+        quote! {
+            ::std::option::Option::Some(::std::cmp::Ord::cmp(self, other))
+        }
+    } else {
+        let fields = strct.fields.iter().map(|field| &field.ident);
+        quote! {
+            #(
+                match ::std::cmp::PartialOrd::partial_cmp(&self.#fields, &other.#fields) {
+                    ::std::option::Option::Some(::std::cmp::Ordering::Equal) => {}
+                    ordering => return ordering,
+                }
+            )*
+            ::std::option::Option::Some(::std::cmp::Ordering::Equal)
+        }
+    };
+
+    quote_spanned! {span=>
+        impl ::std::cmp::PartialOrd for #ident {
+            fn partial_cmp(&self, other: &Self) -> ::std::option::Option<::std::cmp::Ordering> {
+                #body
+            }
+        }
+    }
+}
+
 fn enum_copy(enm: &Enum, span: Span) -> TokenStream {
     let ident = &enm.name.rust;
 
@@ -174,3 +226,27 @@ fn enum_debug(enm: &Enum, span: Span) -> TokenStream {
         }
     }
 }
+
+fn enum_ord(enm: &Enum, span: Span) -> TokenStream {
+    let ident = &enm.name.rust;
+
+    quote_spanned! {span=>
+        impl ::std::cmp::Ord for #ident {
+            pub fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
+                ::std::cmp::Ord::cmp(&self.repr, &other.repr)
+            }
+        }
+    }
+}
+
+fn enum_partial_ord(enm: &Enum, span: Span) -> TokenStream {
+    let ident = &enm.name.rust;
+
+    quote_spanned! {span=>
+        impl ::std::cmp::PartialOrd for #ident {
+            pub fn cmp(&self, other: &Self) -> ::std::option::Option<::std::cmp::Ordering> {
+                ::std::cmp::PartialOrd::cmp(&self.repr, &other.repr)
+            }
+        }
+    }
+}
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index 5020a398..2b9c7d47 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -187,6 +187,49 @@ fn expand_struct_operators(strct: &Struct) -> TokenStream {
                     });
                 }
             }
+            Trait::PartialOrd => {
+                let link_name = mangle::operator(&strct.name, "__operator_lt");
+                let local_name = format_ident!("__operator_lt_{}", strct.name.rust);
+                operators.extend(quote_spanned! {span=>
+                    #[doc(hidden)]
+                    #[export_name = #link_name]
+                    extern "C" fn #local_name(lhs: &#ident, rhs: &#ident) -> bool {
+                        *lhs < *rhs
+                    }
+                });
+
+                let link_name = mangle::operator(&strct.name, "__operator_le");
+                let local_name = format_ident!("__operator_le_{}", strct.name.rust);
+                operators.extend(quote_spanned! {span=>
+                    #[doc(hidden)]
+                    #[export_name = #link_name]
+                    extern "C" fn #local_name(lhs: &#ident, rhs: &#ident) -> bool {
+                        *lhs <= *rhs
+                    }
+                });
+
+                if !derive::contains(&strct.derives, Trait::Ord) {
+                    let link_name = mangle::operator(&strct.name, "__operator_gt");
+                    let local_name = format_ident!("__operator_gt_{}", strct.name.rust);
+                    operators.extend(quote_spanned! {span=>
+                        #[doc(hidden)]
+                        #[export_name = #link_name]
+                        extern "C" fn #local_name(lhs: &#ident, rhs: &#ident) -> bool {
+                            *lhs > *rhs
+                        }
+                    });
+
+                    let link_name = mangle::operator(&strct.name, "__operator_ge");
+                    let local_name = format_ident!("__operator_ge_{}", strct.name.rust);
+                    operators.extend(quote_spanned! {span=>
+                        #[doc(hidden)]
+                        #[export_name = #link_name]
+                        extern "C" fn #local_name(lhs: &#ident, rhs: &#ident) -> bool {
+                            *lhs >= *rhs
+                        }
+                    });
+                }
+            }
             _ => {}
         }
     }
diff --git a/syntax/derive.rs b/syntax/derive.rs
index d258cc7b..10999670 100644
--- a/syntax/derive.rs
+++ b/syntax/derive.rs
@@ -12,7 +12,9 @@ pub enum Trait {
     Copy,
     Debug,
     Eq,
+    Ord,
     PartialEq,
+    PartialOrd,
 }
 
 impl Derive {
@@ -22,7 +24,9 @@ impl Derive {
             "Copy" => Trait::Copy,
             "Debug" => Trait::Debug,
             "Eq" => Trait::Eq,
+            "Ord" => Trait::Ord,
             "PartialEq" => Trait::PartialEq,
+            "PartialOrd" => Trait::PartialOrd,
             _ => return None,
         };
         let span = ident.span();


Test patch:
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index 021b0c34..cb0f916e 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -15,12 +15,12 @@ use std::os::raw::c_char;
 
 #[cxx::bridge(namespace = "tests")]
 pub mod ffi {
-    #[derive(Clone, Debug, PartialEq, Eq)]
+    #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
     struct Shared {
         z: usize,
     }
 
-    #[derive(PartialEq)]
+    #[derive(PartialEq, PartialOrd)]
     struct SharedString {
         msg: String,
     }