Repository: wasm-bindgen/wasm-bindgen (Rust)

Issue:
Enum: Enable annotating discriminant of only some variants
### Motivation
In the codebase I am working on are a couple of C-style enums, which have some variants annotated with discriminants and others not. _(see for example [`enum ColorPrimaries`](
https://github.com/xiph/rav1e/blob/6a4e7eb3445896023a7fd65df5feaefcfd53de10/src/api/color.rs#L41-L73))_

I'd like to export these enums to js, to be able to use them as input parameters for some functions.

But, currently following code doesn't work, because one`must either annotate discriminant of all variants or none`.

```rust
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
#[derive(Debug)]
pub enum Boat {
    Red,
    Blue,
    Green = 42,
    Yellow,
}

#[wasm_bindgen]
pub fn run_it(b: Boat) -> String {
    format!("{:?}", b)
}
```

### Proposed Solution
I'd like `wasm-bindgen` to generate following js-glue-code:
```ts
export const Boat = Object.freeze({
    Red: 0,     "0":  "Red",
    Blue: 1,    "1":  "Blue",
    Green: 42,  "42": "Green",
    Yellow: 43, "43": "Yellow",
});
```
... and following type annotations:
```ts
export enum Boat {
  Red,
  Green,
  Blue,
  Yellow,
}
```

### Alternatives

I could also create a copy of the needed enums, remove all discriminants and then match the copied and actual enum in the function. But this sounds not like a clean and maintainable solution...

### Additional Context

I am happy to implement these changes, but would need a bit of mentoring :smile: 

<details>
<summary>Edit</summary>

Initially I proposed to generate following js-Object:
```js
export const Boat = Object.freeze({
    Red: 0,     "0":  "Red",
    Blue: 1,    "1":  "Blue",
    Green: 42,  "42": "Green",
    Yellow: 2,  "2":  "Yellow",
});
```

But playing around with rust I saw that following code:
```rust
pub enum Boat {
    Red,
    Blue,
    Green = 42,
    Yellow,
}

fn main() {
    let a = Boat::Red as u32;
    let b = Boat::Blue as u32;
    let c = Boat::Green as u32;
    let d = Boat::Yellow as u32;

    for i in [a, b, c, d].iter() {
        eprintln!("{:?}", i);
    }
}
```
... actually outputs:
```
0
1
42
43
```
So I guess it is better to stick with that also for the js code.

</details>


Discussion: Digging a bit into the code, I found that the error message comes from: https://github.com/rustwasm/wasm-bindgen/blob/87663c6d2a442d98b3d8ea6242f20c5c21fc0174/crates/macro-support/src/parser.rs#L1155-L1163

The important bit is "We don't really want to get in the business of emulating how rustc assigns values to enums." (written by @alexcrichton).

Wouldn't it be possible to read the values `rustc` assigns to the enum variants (by casting them to `u32`)?
Currently, no, there's no way to read what rustc assigns a variant.
> Currently, no, there's no way to read what rustc assigns a variant.

@alexcrichton That's a pity :/ Can you think of any other way to make this possible?
Unfortunately, no, I cannot at this time.
> Currently, no, there's no way to read what rustc assigns a variant.

[Implicit discriminants](https://doc.rust-lang.org/reference/items/enumerations.html#implicit-discriminants) are assigned by Rust using a very simple algorithm. We don't need rustc to know which value is assigned, because we know the algorithm it uses.

To copy a code example from the docs:

```rs
enum Foo {
    Bar,            // 0
    Baz = 123,      // 123
    Quux,           // 124
}

let baz_discriminant = Foo::Baz as u32;
assert_eq!(baz_discriminant, 123);
```



Fix (patch):
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7488ff1876e..8172661675f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,15 @@
 # `wasm-bindgen` Change Log
 --------------------------------------------------------------------------------
 
+## Unreleased
+
+### Added
+
+* Added support for implicit discriminants in enums.
+  [#4152](https://github.com/rustwasm/wasm-bindgen/pull/4152)
+
+--------------------------------------------------------------------------------
+
 ## [0.2.94](https://github.com/rustwasm/wasm-bindgen/compare/0.2.93...0.2.94)
 
 Released 2024-10-09
diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs
index b1bf4e1295f..3a36d139d5a 100644
--- a/crates/macro-support/src/parser.rs
+++ b/crates/macro-support/src/parser.rs
@@ -1,5 +1,6 @@
 use std::cell::{Cell, RefCell};
 use std::char;
+use std::collections::HashMap;
 use std::str::Chars;
 
 use ast::OperationKind;
@@ -1399,28 +1400,18 @@ impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum {
             return string_enum(self, program, js_name, generate_typescript, comments);
         }
 
-        let has_discriminant = self.variants[0].discriminant.is_some();
-
         match self.vis {
             syn::Visibility::Public(_) => {}
             _ => bail_span!(self, "only public enums are allowed with #[wasm_bindgen]"),
         }
 
+        let mut last_discriminant: Option<u32> = None;
+        let mut discriminate_map: HashMap<u32, &syn::Variant> = HashMap::new();
+
         let variants = self
             .variants
             .iter()
-            .enumerate()
-            .map(|(i, v)| {
-                // Require that everything either has a discriminant or doesn't.
-                // We don't really want to get in the business of emulating how
-                // rustc assigns values to enums.
-                if v.discriminant.is_some() != has_discriminant {
-                    bail_span!(
-                        v,
-                        "must either annotate discriminant of all variants or none"
-                    );
-                }
-
+            .map(|v| {
                 let value = match &v.discriminant {
                     Some((_, expr)) => match get_expr(expr) {
                         syn::Expr::Lit(syn::ExprLit {
@@ -1442,8 +1433,33 @@ impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum {
                              number literal values",
                         ),
                     },
-                    None => i as u32,
+                    None => {
+                        // Use the same algorithm as rustc to determine the next discriminant
+                        // https://doc.rust-lang.org/reference/items/enumerations.html#implicit-discriminants
+                        if let Some(last) = last_discriminant {
+                            if let Some(value) = last.checked_add(1) {
+                                value
+                            } else {
+                                bail_span!(
+                                    v,
+                                    "the discriminants of C-style enums with #[wasm_bindgen] must be representable as u32"
+                                );
+                            }
+                        } else {
+                            0
+                        }
+                    }
                 };
+                last_discriminant = Some(value);
+
+                if let Some(old) = discriminate_map.insert(value, v) {
+                    bail_span!(
+                        v,
+                        "discriminant value `{}` is already used by {} in this enum",
+                        value,
+                        old.ident
+                    );
+                }
 
                 let comments = extract_doc_comments(&v.attrs);
                 Ok(ast::Variant {
@@ -1454,21 +1470,9 @@ impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum {
             })
             .collect::<Result<Vec<_>, Diagnostic>>()?;
 
-        let mut values = variants.iter().map(|v| v.value).collect::<Vec<_>>();
-        values.sort();
-        let hole = values
-            .windows(2)
-            .find_map(|window| {
-                if window[0] + 1 != window[1] {
-                    Some(window[0] + 1)
-                } else {
-                    None
-                }
-            })
-            .unwrap_or(*values.last().unwrap() + 1);
-        for value in values {
-            assert!(hole != value);
-        }
+        let hole = (0..=u32::MAX)
+            .find(|v| !discriminate_map.contains_key(v))
+            .unwrap();
 
         self.to_tokens(tokens);