Skip to content

Commit 97c021a

Browse files
authored
Merge pull request #36 from rylev/enums
C-Style Enums
2 parents 6a7c971 + f1b300c commit 97c021a

File tree

5 files changed

+224
-2
lines changed

5 files changed

+224
-2
lines changed

crates/wasm-bindgen-cli-support/src/js.rs

+27-1
Original file line numberDiff line numberDiff line change
@@ -978,6 +978,9 @@ impl<'a, 'b> SubContext<'a, 'b> {
978978
for f in self.program.imports.iter() {
979979
self.generate_import(f);
980980
}
981+
for e in self.program.enums.iter() {
982+
self.generate_enum(e);
983+
}
981984
}
982985

983986
pub fn generate_export(&mut self, export: &shared::Export) {
@@ -1051,7 +1054,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
10511054
passed_args.push_str(arg);
10521055
};
10531056
match *arg {
1054-
shared::TYPE_NUMBER => {
1057+
shared::TYPE_ENUM | shared::TYPE_NUMBER => {
10551058
dst_ts.push_str(": number");
10561059
if self.cx.config.debug {
10571060
self.cx.expose_assert_num();
@@ -1156,6 +1159,10 @@ impl<'a, 'b> SubContext<'a, 'b> {
11561159
dst_ts.push_str(": void");
11571160
format!("return ret;")
11581161
}
1162+
Some(shared::TYPE_ENUM) => {
1163+
dst_ts.push_str(": number");
1164+
format!("return ret;")
1165+
}
11591166
Some(shared::TYPE_NUMBER) => {
11601167
dst_ts.push_str(": number");
11611168
format!("return ret;")
@@ -1423,6 +1430,25 @@ impl<'a, 'b> SubContext<'a, 'b> {
14231430
self.cx.globals.push_str(&dst);
14241431
self.cx.globals.push_str("\n");
14251432
}
1433+
1434+
pub fn generate_enum(&mut self, enum_: &shared::Enum) {
1435+
let mut variants = String::new();
1436+
1437+
for variant in enum_.variants.iter() {
1438+
variants.push_str(&format!("{}:{},", variant.name, variant.value));
1439+
}
1440+
self.cx.globals.push_str(&format!("export const {} = {{", enum_.name));
1441+
self.cx.globals.push_str(&variants);
1442+
self.cx.globals.push_str("}\n");
1443+
self.cx.typescript.push_str(&format!("export enum {} {{", enum_.name));
1444+
1445+
variants.clear();
1446+
for variant in enum_.variants.iter() {
1447+
variants.push_str(&format!("{},", variant.name));
1448+
}
1449+
self.cx.typescript.push_str(&variants);
1450+
self.cx.typescript.push_str("}\n");
1451+
}
14261452
}
14271453

14281454
struct VectorType {

crates/wasm-bindgen-macro/src/ast.rs

+56-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use syn;
99
pub struct Program {
1010
pub exports: Vec<Export>,
1111
pub imports: Vec<Import>,
12+
pub enums: Vec<Enum>,
1213
pub imported_types: Vec<(syn::Visibility, syn::Ident)>,
1314
pub structs: Vec<Struct>,
1415
}
@@ -47,6 +48,11 @@ pub struct Struct {
4748
pub name: syn::Ident,
4849
}
4950

51+
pub struct Enum {
52+
pub name: syn::Ident,
53+
pub variants: Vec<(syn::Ident, u32)>
54+
}
55+
5056
pub enum Type {
5157
// special
5258
Vector(VectorType, bool),
@@ -110,8 +116,13 @@ impl Program {
110116
let opts = opts.unwrap_or_else(|| BindgenAttrs::find(&mut f.attrs));
111117
self.push_foreign_mod(f, opts);
112118
}
119+
syn::Item::Enum(mut e) => {
120+
let opts = opts.unwrap_or_else(|| BindgenAttrs::find(&mut e.attrs));
121+
e.to_tokens(tokens);
122+
self.push_enum(e, opts);
123+
}
113124
_ => panic!("#[wasm_bindgen] can only be applied to a function, \
114-
struct, impl, or extern block"),
125+
struct, enum, impl, or extern block"),
115126
}
116127
}
117128

@@ -195,6 +206,36 @@ impl Program {
195206
}
196207
}
197208

209+
pub fn push_enum(&mut self, item: syn::ItemEnum, _opts: BindgenAttrs) {
210+
match item.vis {
211+
syn::Visibility::Public(_) => {}
212+
_ => panic!("only public enums are allowed"),
213+
}
214+
215+
let variants = item.variants.iter().enumerate().map(|(i, v)| {
216+
match v.fields {
217+
syn::Fields::Unit => (),
218+
_ => panic!("Only C-Style enums allowed")
219+
}
220+
let value = match v.discriminant {
221+
Some((_, syn::Expr::Lit(syn::ExprLit {attrs: _, lit: syn::Lit::Int(ref int_lit)}))) => {
222+
if int_lit.value() > <u32>::max_value() as u64 {
223+
panic!("Enums can only support numbers that can be represented as u32");
224+
}
225+
int_lit.value() as u32
226+
},
227+
None => i as u32,
228+
_ => panic!("Enums may only have number literal values")
229+
};
230+
231+
(v.ident, value)
232+
}).collect();
233+
self.enums.push(Enum {
234+
name: item.ident,
235+
variants
236+
});
237+
}
238+
198239
pub fn push_foreign_fn(&mut self,
199240
mut f: syn::ForeignItemFn,
200241
module_opts: &BindgenAttrs) {
@@ -297,6 +338,7 @@ impl Program {
297338
a.fields(&[
298339
("exports", &|a| a.list(&self.exports, Export::wbg_literal)),
299340
("imports", &|a| a.list(&self.imports, Import::wbg_literal)),
341+
("enums", &|a| a.list(&self.enums, Enum::wbg_literal)),
300342
("custom_type_names", &|a| {
301343
let names = self.exports.iter()
302344
.filter_map(|e| e.class)
@@ -634,6 +676,19 @@ impl Import {
634676
}
635677
}
636678

679+
impl Enum {
680+
fn wbg_literal(&self, a: &mut LiteralBuilder) {
681+
a.fields(&[
682+
("name", &|a| a.str(self.name.as_ref())),
683+
("variants", &|a| a.list(&self.variants, |v, a| {
684+
let &(name, value) = v;
685+
a.fields(&[("name", &|a| a.str(name.as_ref())),
686+
("value", &|a| a.append(&format!("{}", value)))])
687+
})),
688+
]);
689+
}
690+
}
691+
637692
impl Struct {
638693
fn from(s: syn::ItemStruct, _opts: BindgenAttrs) -> Struct {
639694
Struct { name: s.ident }

crates/wasm-bindgen-macro/src/lib.rs

+40
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ fn generate_wrappers(program: ast::Program, tokens: &mut Tokens) {
5050
for i in program.imports.iter() {
5151
bindgen_import(i, tokens);
5252
}
53+
for e in program.enums.iter() {
54+
bindgen_enum(e, tokens);
55+
}
5356
for &(ref vis, ref t) in program.imported_types.iter() {
5457
bindgen_imported_type(vis, t, tokens);
5558
}
@@ -510,3 +513,40 @@ fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) {
510513
invocation.to_tokens(tokens);
511514
}
512515
}
516+
517+
fn bindgen_enum(e: &ast::Enum, into: &mut Tokens) {
518+
let enum_name = &e.name;
519+
let c = shared::TYPE_ENUM as u32;
520+
let incoming_u32 = quote! { n };
521+
let enum_name_as_string = enum_name.to_string();
522+
let cast_clauses = e.variants.iter().map(|variant| {
523+
let &(variant_name, _) = variant;
524+
quote! {
525+
if #incoming_u32 == #enum_name::#variant_name as u32 {
526+
#enum_name::#variant_name
527+
}
528+
}
529+
});
530+
(my_quote! {
531+
impl #enum_name {
532+
fn from_u32(#incoming_u32: u32) -> #enum_name {
533+
#(#cast_clauses else)* {
534+
wasm_bindgen::throw(&format!("Could not cast {} as {}", #incoming_u32, #enum_name_as_string));
535+
}
536+
}
537+
}
538+
539+
impl ::wasm_bindgen::convert::WasmBoundary for #enum_name {
540+
type Js = u32;
541+
const DESCRIPTOR: u32 = #c;
542+
543+
fn into_js(self) -> u32 {
544+
self as u32
545+
}
546+
547+
unsafe fn from_js(js: u32) -> Self {
548+
#enum_name::from_u32(js)
549+
}
550+
}
551+
}).to_tokens(into);
552+
}

crates/wasm-bindgen-shared/src/lib.rs

+14
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::hash::{Hash, Hasher};
88
#[derive(Deserialize)]
99
pub struct Program {
1010
pub exports: Vec<Export>,
11+
pub enums: Vec<Enum>,
1112
pub imports: Vec<Import>,
1213
pub custom_type_names: Vec<CustomTypeName>,
1314
}
@@ -32,6 +33,18 @@ pub struct Export {
3233
pub function: Function,
3334
}
3435

36+
#[derive(Deserialize)]
37+
pub struct Enum {
38+
pub name: String,
39+
pub variants: Vec<EnumVariant>,
40+
}
41+
42+
#[derive(Deserialize)]
43+
pub struct EnumVariant {
44+
pub name: String,
45+
pub value: u32
46+
}
47+
3548
#[derive(Deserialize)]
3649
pub struct Function {
3750
pub name: String,
@@ -77,6 +90,7 @@ pub fn mangled_import_name(struct_: Option<&str>, f: &str) -> String {
7790

7891
pub type Type = char;
7992

93+
pub const TYPE_ENUM: char = '\u{5d}';
8094
pub const TYPE_NUMBER: char = '\u{5e}';
8195
pub const TYPE_BORROWED_STR: char = '\u{5f}';
8296
pub const TYPE_STRING: char = '\u{60}';

tests/enums.rs

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
extern crate test_support;
2+
3+
#[test]
4+
fn c_style_enum() {
5+
test_support::project()
6+
.file("src/lib.rs", r#"
7+
#![feature(proc_macro)]
8+
9+
extern crate wasm_bindgen;
10+
11+
use wasm_bindgen::prelude::*;
12+
13+
#[wasm_bindgen]
14+
pub enum Color {
15+
Green,
16+
Yellow,
17+
Red,
18+
}
19+
20+
#[no_mangle]
21+
#[wasm_bindgen]
22+
pub extern fn cycle(color: Color) -> Color {
23+
match color {
24+
Color::Green => Color::Yellow,
25+
Color::Yellow => Color::Red,
26+
Color::Red => Color::Green,
27+
}
28+
}
29+
"#)
30+
.file("test.ts", r#"
31+
import * as assert from "assert";
32+
import * as wasm from "./out";
33+
34+
export function test() {
35+
assert.strictEqual(wasm.Color.Green, 0);
36+
assert.strictEqual(wasm.Color.Yellow, 1);
37+
assert.strictEqual(wasm.Color.Red, 2);
38+
assert.strictEqual(Object.keys(wasm.Color).length, 3);
39+
40+
assert.strictEqual(wasm.cycle(wasm.Color.Green), wasm.Color.Yellow);
41+
}
42+
"#)
43+
.test();
44+
}
45+
46+
#[test]
47+
fn c_style_enum_with_custom_values() {
48+
test_support::project()
49+
.file("src/lib.rs", r#"
50+
#![feature(proc_macro)]
51+
52+
extern crate wasm_bindgen;
53+
54+
use wasm_bindgen::prelude::*;
55+
56+
#[wasm_bindgen]
57+
pub enum Color {
58+
Green = 21,
59+
Yellow = 34,
60+
Red,
61+
}
62+
63+
#[no_mangle]
64+
#[wasm_bindgen]
65+
pub extern fn cycle(color: Color) -> Color {
66+
match color {
67+
Color::Green => Color::Yellow,
68+
Color::Yellow => Color::Red,
69+
Color::Red => Color::Green,
70+
}
71+
}
72+
"#)
73+
.file("test.ts", r#"
74+
import * as assert from "assert";
75+
import * as wasm from "./out";
76+
77+
export function test() {
78+
assert.strictEqual(wasm.Color.Green, 21);
79+
assert.strictEqual(wasm.Color.Yellow, 34);
80+
assert.strictEqual(wasm.Color.Red, 2);
81+
assert.strictEqual(Object.keys(wasm.Color).length, 3);
82+
83+
assert.strictEqual(wasm.cycle(wasm.Color.Green), wasm.Color.Yellow);
84+
}
85+
"#)
86+
.test();
87+
}

0 commit comments

Comments
 (0)