Skip to content

Commit 02679e1

Browse files
Add support for C-style enums with implicit discriminants (#4152)
1 parent 35d1c63 commit 02679e1

File tree

7 files changed

+109
-32
lines changed

7 files changed

+109
-32
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
# `wasm-bindgen` Change Log
22
--------------------------------------------------------------------------------
33

4+
## Unreleased
5+
6+
### Added
7+
8+
* Added support for implicit discriminants in enums.
9+
[#4152](https://github.com/rustwasm/wasm-bindgen/pull/4152)
10+
11+
--------------------------------------------------------------------------------
12+
413
## [0.2.94](https://github.com/rustwasm/wasm-bindgen/compare/0.2.93...0.2.94)
514

615
Released 2024-10-09

crates/cli/tests/reference/enums.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ export enum Color {
3737
*/
3838
Red = 2,
3939
}
40+
export enum ImplicitDiscriminant {
41+
A = 0,
42+
B = 1,
43+
C = 42,
44+
D = 43,
45+
}
4046
/**
4147
* The name of a color.
4248
*/

crates/cli/tests/reference/enums.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ Yellow:1,"1":"Yellow",
7979
*/
8080
Red:2,"2":"Red", });
8181

82+
export const ImplicitDiscriminant = Object.freeze({ A:0,"0":"A",B:1,"1":"B",C:42,"42":"C",D:43,"43":"D", });
83+
8284
const __wbindgen_enum_ColorName = ["green", "yellow", "red"];
8385

8486
const __wbindgen_enum_FooBar = ["foo", "bar"];

crates/cli/tests/reference/enums.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,21 @@ pub fn option_string_enum_echo(color: Option<ColorName>) -> Option<ColorName> {
4747

4848
/// An unused string enum.
4949
#[wasm_bindgen(js_name = "FooBar")]
50-
#[derive(PartialEq, Debug)]
5150
pub enum UnusedStringEnum {
5251
Foo = "foo",
5352
Bar = "bar",
5453
}
5554

5655
#[wasm_bindgen]
57-
#[derive(PartialEq, Debug)]
5856
enum PrivateStringEnum {
5957
Foo = "foo",
6058
Bar = "bar",
6159
}
60+
61+
#[wasm_bindgen]
62+
pub enum ImplicitDiscriminant {
63+
A,
64+
B,
65+
C = 42,
66+
D,
67+
}

crates/macro-support/src/parser.rs

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::cell::{Cell, RefCell};
22
use std::char;
3+
use std::collections::HashMap;
34
use std::str::Chars;
45

56
use ast::OperationKind;
@@ -1399,28 +1400,18 @@ impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum {
13991400
return string_enum(self, program, js_name, generate_typescript, comments);
14001401
}
14011402

1402-
let has_discriminant = self.variants[0].discriminant.is_some();
1403-
14041403
match self.vis {
14051404
syn::Visibility::Public(_) => {}
14061405
_ => bail_span!(self, "only public enums are allowed with #[wasm_bindgen]"),
14071406
}
14081407

1408+
let mut last_discriminant: Option<u32> = None;
1409+
let mut discriminate_map: HashMap<u32, &syn::Variant> = HashMap::new();
1410+
14091411
let variants = self
14101412
.variants
14111413
.iter()
1412-
.enumerate()
1413-
.map(|(i, v)| {
1414-
// Require that everything either has a discriminant or doesn't.
1415-
// We don't really want to get in the business of emulating how
1416-
// rustc assigns values to enums.
1417-
if v.discriminant.is_some() != has_discriminant {
1418-
bail_span!(
1419-
v,
1420-
"must either annotate discriminant of all variants or none"
1421-
);
1422-
}
1423-
1414+
.map(|v| {
14241415
let value = match &v.discriminant {
14251416
Some((_, expr)) => match get_expr(expr) {
14261417
syn::Expr::Lit(syn::ExprLit {
@@ -1442,8 +1433,33 @@ impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum {
14421433
number literal values",
14431434
),
14441435
},
1445-
None => i as u32,
1436+
None => {
1437+
// Use the same algorithm as rustc to determine the next discriminant
1438+
// https://doc.rust-lang.org/reference/items/enumerations.html#implicit-discriminants
1439+
if let Some(last) = last_discriminant {
1440+
if let Some(value) = last.checked_add(1) {
1441+
value
1442+
} else {
1443+
bail_span!(
1444+
v,
1445+
"the discriminants of C-style enums with #[wasm_bindgen] must be representable as u32"
1446+
);
1447+
}
1448+
} else {
1449+
0
1450+
}
1451+
}
14461452
};
1453+
last_discriminant = Some(value);
1454+
1455+
if let Some(old) = discriminate_map.insert(value, v) {
1456+
bail_span!(
1457+
v,
1458+
"discriminant value `{}` is already used by {} in this enum",
1459+
value,
1460+
old.ident
1461+
);
1462+
}
14471463

14481464
let comments = extract_doc_comments(&v.attrs);
14491465
Ok(ast::Variant {
@@ -1454,21 +1470,9 @@ impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum {
14541470
})
14551471
.collect::<Result<Vec<_>, Diagnostic>>()?;
14561472

1457-
let mut values = variants.iter().map(|v| v.value).collect::<Vec<_>>();
1458-
values.sort();
1459-
let hole = values
1460-
.windows(2)
1461-
.find_map(|window| {
1462-
if window[0] + 1 != window[1] {
1463-
Some(window[0] + 1)
1464-
} else {
1465-
None
1466-
}
1467-
})
1468-
.unwrap_or(*values.last().unwrap() + 1);
1469-
for value in values {
1470-
assert!(hole != value);
1471-
}
1473+
let hole = (0..=u32::MAX)
1474+
.find(|v| !discriminate_map.contains_key(v))
1475+
.unwrap();
14721476

14731477
self.to_tokens(tokens);
14741478

crates/macro/ui-tests/invalid-enums.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,30 @@ enum G {
3737
C,
3838
}
3939

40+
#[wasm_bindgen]
41+
pub enum H {
42+
A = 1,
43+
B = 1, // collision
44+
}
45+
46+
#[wasm_bindgen]
47+
pub enum I {
48+
A = 4294967294, // = u32::MAX - 1
49+
B, // would be u32::MAX
50+
C, // would be u32::MAX + 1
51+
}
52+
53+
#[wasm_bindgen]
54+
pub enum J {
55+
A, // = 0
56+
B = 0, // collision
57+
}
58+
59+
#[wasm_bindgen]
60+
pub enum K {
61+
A = 3,
62+
B = 2,
63+
C, // = 3 -> collision
64+
}
65+
4066
fn main() {}

crates/macro/ui-tests/invalid-enums.stderr

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,27 @@ error: all variants of a string enum must have a string value
3939
|
4040
37 | C,
4141
| ^
42+
43+
error: discriminant value `1` is already used by A in this enum
44+
--> ui-tests/invalid-enums.rs:43:5
45+
|
46+
43 | B = 1, // collision
47+
| ^^^^^
48+
49+
error: the discriminants of C-style enums with #[wasm_bindgen] must be representable as u32
50+
--> ui-tests/invalid-enums.rs:50:5
51+
|
52+
50 | C, // would be u32::MAX + 1
53+
| ^
54+
55+
error: discriminant value `0` is already used by A in this enum
56+
--> ui-tests/invalid-enums.rs:56:5
57+
|
58+
56 | B = 0, // collision
59+
| ^^^^^
60+
61+
error: discriminant value `3` is already used by A in this enum
62+
--> ui-tests/invalid-enums.rs:63:5
63+
|
64+
63 | C, // = 3 -> collision
65+
| ^

0 commit comments

Comments
 (0)