Repository: dtolnay/cxx (Rust)

Issue:
Derive bitwise BitAnd and BitOr for shared enums
C++ enums come with bit operators:

```cpp
enum Enum {
  A = 1,
  B = 2,
  C = 4,
};

int main() {
  auto x = Enum::A | Enum::B;
}
```

We should provide a way to expose the same on the Rust side via derived impls of https://doc.rust-lang.org/std/ops/trait.BitAnd.html and https://doc.rust-lang.org/std/ops/trait.BitOr.html.

```rust
#[cxx::bridge]
mod ffi {
    #[derive(BitAnd, BitOr)]
    enum Enum {
        A = 1,
        B = 2,
        C = 4,
    }
}
```


Discussion: until it is implemented as part of the language itself (if ever), a bit of `as` magic does the job without any bitor/bitand trait implementation (makes the code a bit longer but it is very simple)

````rust
enum BITS {
    ONE = 0b0001,
    TWO = 0b0010,
    FOUR = 0b0100,
    EIGHT = 0b1000,
}

fn main() {
    let flags: u8;

    flags = BITS::ONE as u8 | BITS::FOUR as u8 | BITS::EIGHT as u8;

    println!("{flags:b}")
}
````

PS: you will get warning of this shape if you don't use all enums in the code: "warning: variant `TWO` is never constructed", but, again, it is just a warning that can be handled gracefully.

edit: changed i8 to u8 to use 8th bit
PS: Do not confuse if you see this at SO as I have it at https://stackoverflow.com/a/76603396/9512475



Fix (patch):
diff --git a/book/src/shared.md b/book/src/shared.md
index 368b6515c..bec087e9e 100644
--- a/book/src/shared.md
+++ b/book/src/shared.md
@@ -215,6 +215,9 @@ bridge module.
 - `Ord`
 - `PartialEq`
 - `PartialOrd`
+- `BitAnd` (enums only)
+- `BitOr` (enums only)
+- `BitXor` (enums only)
 
 Note that shared enums automatically always come with impls of `Copy`, `Clone`,
 `Eq`, and `PartialEq`, so you're free to omit those derives on an enum.
@@ -242,6 +245,9 @@ C++ data type:
 - `Hash` gives you a specialization of [`template <> struct std::hash<T>`][hash] in C++
 - `PartialEq` produces `operator==` and `operator!=`
 - `PartialOrd` produces `operator<`, `operator<=`, `operator>`, `operator>=`
+- `BitAnd` produces `operator&`
+- `BitOr` produces `operator|`
+- `BitXor` produces `operator^`
 
 [hash]: https://en.cppreference.com/w/cpp/utility/hash
 
diff --git a/gen/src/out.rs b/gen/src/out.rs
index 382482572..c18fd11bd 100644
--- a/gen/src/out.rs
+++ b/gen/src/out.rs
@@ -23,6 +23,7 @@ pub(crate) struct Content<'a> {
     bytes: String,
     namespace: &'a Namespace,
     blocks: Vec<BlockBoundary<'a>>,
+    suppress_next_section: bool,
     section_pending: bool,
     blocks_pending: usize,
 }
@@ -51,6 +52,10 @@ impl<'a> OutFile<'a> {
         self.content.get_mut().next_section();
     }
 
+    pub(crate) fn suppress_next_section(&mut self) {
+        self.content.get_mut().suppress_next_section();
+    }
+
     pub(crate) fn begin_block(&mut self, block: Block<'a>) {
         self.content.get_mut().begin_block(block);
     }
@@ -129,7 +134,11 @@ impl<'a> Content<'a> {
     }
 
     pub(crate) fn next_section(&mut self) {
-        self.section_pending = true;
+        self.section_pending = !self.suppress_next_section;
+    }
+
+    pub(crate) fn suppress_next_section(&mut self) {
+        self.suppress_next_section = true;
     }
 
     pub(crate) fn begin_block(&mut self, block: Block<'a>) {
@@ -163,6 +172,7 @@ impl<'a> Content<'a> {
                 self.bytes.push('\n');
             }
             self.bytes.push_str(b);
+            self.suppress_next_section = false;
             self.section_pending = false;
             self.blocks_pending = 0;
         }
diff --git a/gen/src/write.rs b/gen/src/write.rs
index 6ec382e32..55370ac33 100644
--- a/gen/src/write.rs
+++ b/gen/src/write.rs
@@ -447,6 +447,7 @@ fn write_enum<'a>(out: &mut OutFile<'a>, enm: &'a Enum) {
     let guard = Guard::new(out, "CXXBRIDGE1_ENUM", &enm.name);
     writeln!(out, "#ifndef {}", guard);
     writeln!(out, "#define {}", guard);
+
     write_doc(out, "", &enm.doc);
     write!(out, "enum class {} : ", enm.name.cxx);
     write_atom(out, enm.repr.atom);
@@ -458,6 +459,11 @@ fn write_enum<'a>(out: &mut OutFile<'a>, enm: &'a Enum) {
         writeln!(out, ",");
     }
     writeln!(out, "}};");
+
+    if out.header {
+        write_enum_operators(out, enm);
+    }
+
     writeln!(out, "#endif // {}", guard);
 }
 
@@ -479,6 +485,20 @@ fn check_enum<'a>(out: &mut OutFile<'a>, enm: &'a Enum) {
         write_discriminant(out, enm.repr.atom, variant.discriminant);
         writeln!(out, ", \"disagrees with the value in #[cxx::bridge]\");");
     }
+
+    if out.header
+        && (derive::contains(&enm.derives, Trait::BitAnd)
+            || derive::contains(&enm.derives, Trait::BitOr)
+            || derive::contains(&enm.derives, Trait::BitXor))
+    {
+        out.next_section();
+        let guard = Guard::new(out, "CXXBRIDGE1_ENUM", &enm.name);
+        writeln!(out, "#ifndef {}", guard);
+        writeln!(out, "#define {}", guard);
+        out.suppress_next_section();
+        write_enum_operators(out, enm);
+        writeln!(out, "#endif // {}", guard);
+    }
 }
 
 fn write_discriminant(out: &mut OutFile, repr: Atom, discriminant: Discriminant) {
@@ -493,6 +513,53 @@ fn write_discriminant(out: &mut OutFile, repr: Atom, discriminant: Discriminant)
     }
 }
 
+fn write_enum_operators(out: &mut OutFile, enm: &Enum) {
+    if derive::contains(&enm.derives, Trait::BitAnd) {
+        out.next_section();
+        writeln!(
+            out,
+            "inline {} operator&({} lhs, {} rhs) {{",
+            enm.name.cxx, enm.name.cxx, enm.name.cxx,
+        );
+        write!(out, "  return static_cast<{}>(static_cast<", enm.name.cxx);
+        write_atom(out, enm.repr.atom);
+        write!(out, ">(lhs) & static_cast<");
+        write_atom(out, enm.repr.atom);
+        writeln!(out, ">(rhs));");
+        writeln!(out, "}}");
+    }
+
+    if derive::contains(&enm.derives, Trait::BitOr) {
+        out.next_section();
+        writeln!(
+            out,
+            "inline {} operator|({} lhs, {} rhs) {{",
+            enm.name.cxx, enm.name.cxx, enm.name.cxx,
+        );
+        write!(out, "  return static_cast<{}>(static_cast<", enm.name.cxx);
+        write_atom(out, enm.repr.atom);
+        write!(out, ">(lhs) | static_cast<");
+        write_atom(out, enm.repr.atom);
+        writeln!(out, ">(rhs));");
+        writeln!(out, "}}");
+    }
+
+    if derive::contains(&enm.derives, Trait::BitXor) {
+        out.next_section();
+        writeln!(
+            out,
+            "inline {} operator^({} lhs, {} rhs) {{",
+            enm.name.cxx, enm.name.cxx, enm.name.cxx,
+        );
+        write!(out, "  return static_cast<{}>(static_cast<", enm.name.cxx);
+        write_atom(out, enm.repr.atom);
+        write!(out, ">(lhs) ^ static_cast<");
+        write_atom(out, enm.repr.atom);
+        writeln!(out, ">(rhs));");
+        writeln!(out, "}}");
+    }
+}
+
 fn check_trivial_extern_type(out: &mut OutFile, alias: &TypeAlias, reasons: &[TrivialReason]) {
     // NOTE: The following static assertion is just nice-to-have and not
     // necessary for soundness. That's because triviality is always declared by
diff --git a/macro/src/derive.rs b/macro/src/derive.rs
index 5ae013769..61e87236f 100644
--- a/macro/src/derive.rs
+++ b/macro/src/derive.rs
@@ -14,6 +14,9 @@ pub(crate) fn expand_struct(
     for derive in &strct.derives {
         let span = derive.span;
         match derive.what {
+            Trait::BitAnd => unreachable!(),
+            Trait::BitOr => unreachable!(),
+            Trait::BitXor => unreachable!(),
             Trait::Copy => expanded.extend(struct_copy(strct, span)),
             Trait::Clone => expanded.extend(struct_clone(strct, span)),
             Trait::Debug => expanded.extend(struct_debug(strct, span)),
@@ -49,6 +52,9 @@ pub(crate) fn expand_enum(enm: &Enum, actual_derives: &mut Option<TokenStream>)
     for derive in &enm.derives {
         let span = derive.span;
         match derive.what {
+            Trait::BitAnd => expanded.extend(enum_bitand(enm, span)),
+            Trait::BitOr => expanded.extend(enum_bitor(enm, span)),
+            Trait::BitXor => expanded.extend(enum_bitxor(enm, span)),
             Trait::Copy => {
                 expanded.extend(enum_copy(enm, span));
                 has_copy = true;
@@ -241,6 +247,60 @@ fn struct_partial_ord(strct: &Struct, span: Span) -> TokenStream {
     }
 }
 
+fn enum_bitand(enm: &Enum, span: Span) -> TokenStream {
+    let ident = &enm.name.rust;
+    let cfg_and_lint_attrs = enm.attrs.cfg_and_lint();
+
+    quote_spanned! {span=>
+        #cfg_and_lint_attrs
+        #[automatically_derived]
+        impl ::cxx::core::ops::BitAnd for #ident {
+            type Output = #ident;
+            fn bitand(self, rhs: Self) -> Self::Output {
+                #ident {
+                    repr: self.repr & rhs.repr,
+                }
+            }
+        }
+    }
+}
+
+fn enum_bitor(enm: &Enum, span: Span) -> TokenStream {
+    let ident = &enm.name.rust;
+    let cfg_and_lint_attrs = enm.attrs.cfg_and_lint();
+
+    quote_spanned! {span=>
+        #cfg_and_lint_attrs
+        #[automatically_derived]
+        impl ::cxx::core::ops::BitOr for #ident {
+            type Output = #ident;
+            fn bitor(self, rhs: Self) -> Self::Output {
+                #ident {
+                    repr: self.repr | rhs.repr,
+                }
+            }
+        }
+    }
+}
+
+fn enum_bitxor(enm: &Enum, span: Span) -> TokenStream {
+    let ident = &enm.name.rust;
+    let cfg_and_lint_attrs = enm.attrs.cfg_and_lint();
+
+    quote_spanned! {span=>
+        #cfg_and_lint_attrs
+        #[automatically_derived]
+        impl ::cxx::core::ops::BitXor for #ident {
+            type Output = #ident;
+            fn bitxor(self, rhs: Self) -> Self::Output {
+                #ident {
+                    repr: self.repr ^ rhs.repr,
+                }
+            }
+        }
+    }
+}
+
 fn enum_copy(enm: &Enum, span: Span) -> TokenStream {
     let ident = &enm.name.rust;
     let cfg_and_lint_attrs = enm.attrs.cfg_and_lint();
diff --git a/syntax/check.rs b/syntax/check.rs
index cd7a409e0..9ae45bf29 100644
--- a/syntax/check.rs
+++ b/syntax/check.rs
@@ -355,6 +355,13 @@ fn check_api_struct(cx: &mut Check, strct: &Struct) {
             | Trait::PartialOrd
             | Trait::Serialize
             | Trait::Deserialize => {}
+            Trait::BitAnd | Trait::BitOr | Trait::BitXor => {
+                let msg = format!(
+                    "derive({}) is currently only supported on enums, not structs",
+                    derive,
+                );
+                cx.error(derive, msg);
+            }
             Trait::ExternType => {
                 let msg = format!("derive({}) on shared struct is not supported", derive);
                 cx.error(derive, msg);
@@ -390,7 +397,10 @@ fn check_api_enum(cx: &mut Check, enm: &Enum) {
 
     for derive in &enm.derives {
         match derive.what {
-            Trait::Clone
+            Trait::BitAnd
+            | Trait::BitOr
+            | Trait::BitXor
+            | Trait::Clone
             | Trait::Copy
             | Trait::Debug
             | Trait::Eq
diff --git a/syntax/derive.rs b/syntax/derive.rs
index 9e09461c3..641263020 100644
--- a/syntax/derive.rs
+++ b/syntax/derive.rs
@@ -9,6 +9,9 @@ pub(crate) struct Derive {
 
 #[derive(Copy, Clone, PartialEq)]
 pub(crate) enum Trait {
+    BitAnd,
+    BitOr,
+    BitXor,
     Clone,
     Copy,
     Debug,
@@ -26,6 +29,9 @@ pub(crate) enum Trait {
 impl Derive {
     pub(crate) fn from(ident: &Ident) -> Option<Self> {
         let what = match ident.to_string().as_str() {
+            "BitAnd" => Trait::BitAnd,
+            "BitOr" => Trait::BitOr,
+            "BitXor" => Trait::BitXor,
             "Clone" => Trait::Clone,
             "Copy" => Trait::Copy,
             "Debug" => Trait::Debug,
@@ -54,6 +60,9 @@ impl PartialEq<Trait> for Derive {
 impl AsRef<str> for Trait {
     fn as_ref(&self) -> &str {
         match self {
+            Trait::BitAnd => "BitAnd",
+            Trait::BitOr => "BitOr",
+            Trait::BitXor => "BitXor",
             Trait::Clone => "Clone",
             Trait::Copy => "Copy",
             Trait::Debug => "Debug",