diff --git a/crates/wasm-bindgen-cli-support/src/js.rs b/crates/wasm-bindgen-cli-support/src/js.rs index 3108beb6..b19767e2 100644 --- a/crates/wasm-bindgen-cli-support/src/js.rs +++ b/crates/wasm-bindgen-cli-support/src/js.rs @@ -978,6 +978,9 @@ impl<'a, 'b> SubContext<'a, 'b> { for f in self.program.imports.iter() { self.generate_import(f); } + for e in self.program.enums.iter() { + self.generate_enum(e); + } } pub fn generate_export(&mut self, export: &shared::Export) { @@ -1051,7 +1054,7 @@ impl<'a, 'b> SubContext<'a, 'b> { passed_args.push_str(arg); }; match *arg { - shared::TYPE_NUMBER => { + shared::TYPE_ENUM | shared::TYPE_NUMBER => { dst_ts.push_str(": number"); if self.cx.config.debug { self.cx.expose_assert_num(); @@ -1156,6 +1159,10 @@ impl<'a, 'b> SubContext<'a, 'b> { dst_ts.push_str(": void"); format!("return ret;") } + Some(shared::TYPE_ENUM) => { + dst_ts.push_str(": number"); + format!("return ret;") + } Some(shared::TYPE_NUMBER) => { dst_ts.push_str(": number"); format!("return ret;") @@ -1423,6 +1430,25 @@ impl<'a, 'b> SubContext<'a, 'b> { self.cx.globals.push_str(&dst); self.cx.globals.push_str("\n"); } + + pub fn generate_enum(&mut self, enum_: &shared::Enum) { + let mut variants = String::new(); + + for variant in enum_.variants.iter() { + variants.push_str(&format!("{}:{},", variant.name, variant.value)); + } + self.cx.globals.push_str(&format!("export const {} = {{", enum_.name)); + self.cx.globals.push_str(&variants); + self.cx.globals.push_str("}\n"); + self.cx.typescript.push_str(&format!("export enum {} {{", enum_.name)); + + variants.clear(); + for variant in enum_.variants.iter() { + variants.push_str(&format!("{},", variant.name)); + } + self.cx.typescript.push_str(&variants); + self.cx.typescript.push_str("}\n"); + } } struct VectorType { diff --git a/crates/wasm-bindgen-macro/src/ast.rs b/crates/wasm-bindgen-macro/src/ast.rs index 0920cdac..7abacbde 100644 --- a/crates/wasm-bindgen-macro/src/ast.rs +++ b/crates/wasm-bindgen-macro/src/ast.rs @@ -9,6 +9,7 @@ use syn; pub struct Program { pub exports: Vec, pub imports: Vec, + pub enums: Vec, pub imported_types: Vec<(syn::Visibility, syn::Ident)>, pub structs: Vec, } @@ -47,6 +48,11 @@ pub struct Struct { pub name: syn::Ident, } +pub struct Enum { + pub name: syn::Ident, + pub variants: Vec<(syn::Ident, u32)> +} + pub enum Type { // special Vector(VectorType, bool), @@ -110,8 +116,13 @@ impl Program { let opts = opts.unwrap_or_else(|| BindgenAttrs::find(&mut f.attrs)); self.push_foreign_mod(f, opts); } + syn::Item::Enum(mut e) => { + let opts = opts.unwrap_or_else(|| BindgenAttrs::find(&mut e.attrs)); + e.to_tokens(tokens); + self.push_enum(e, opts); + } _ => panic!("#[wasm_bindgen] can only be applied to a function, \ - struct, impl, or extern block"), + struct, enum, impl, or extern block"), } } @@ -195,6 +206,36 @@ impl Program { } } + pub fn push_enum(&mut self, item: syn::ItemEnum, _opts: BindgenAttrs) { + match item.vis { + syn::Visibility::Public(_) => {} + _ => panic!("only public enums are allowed"), + } + + let variants = item.variants.iter().enumerate().map(|(i, v)| { + match v.fields { + syn::Fields::Unit => (), + _ => panic!("Only C-Style enums allowed") + } + let value = match v.discriminant { + Some((_, syn::Expr::Lit(syn::ExprLit {attrs: _, lit: syn::Lit::Int(ref int_lit)}))) => { + if int_lit.value() > ::max_value() as u64 { + panic!("Enums can only support numbers that can be represented as u32"); + } + int_lit.value() as u32 + }, + None => i as u32, + _ => panic!("Enums may only have number literal values") + }; + + (v.ident, value) + }).collect(); + self.enums.push(Enum { + name: item.ident, + variants + }); + } + pub fn push_foreign_fn(&mut self, mut f: syn::ForeignItemFn, module_opts: &BindgenAttrs) { @@ -297,6 +338,7 @@ impl Program { a.fields(&[ ("exports", &|a| a.list(&self.exports, Export::wbg_literal)), ("imports", &|a| a.list(&self.imports, Import::wbg_literal)), + ("enums", &|a| a.list(&self.enums, Enum::wbg_literal)), ("custom_type_names", &|a| { let names = self.exports.iter() .filter_map(|e| e.class) @@ -634,6 +676,19 @@ impl Import { } } +impl Enum { + fn wbg_literal(&self, a: &mut LiteralBuilder) { + a.fields(&[ + ("name", &|a| a.str(self.name.as_ref())), + ("variants", &|a| a.list(&self.variants, |v, a| { + let &(name, value) = v; + a.fields(&[("name", &|a| a.str(name.as_ref())), + ("value", &|a| a.append(&format!("{}", value)))]) + })), + ]); + } +} + impl Struct { fn from(s: syn::ItemStruct, _opts: BindgenAttrs) -> Struct { Struct { name: s.ident } diff --git a/crates/wasm-bindgen-macro/src/lib.rs b/crates/wasm-bindgen-macro/src/lib.rs index 1878bb00..13544e5b 100644 --- a/crates/wasm-bindgen-macro/src/lib.rs +++ b/crates/wasm-bindgen-macro/src/lib.rs @@ -50,6 +50,9 @@ fn generate_wrappers(program: ast::Program, tokens: &mut Tokens) { for i in program.imports.iter() { bindgen_import(i, tokens); } + for e in program.enums.iter() { + bindgen_enum(e, tokens); + } for &(ref vis, ref t) in program.imported_types.iter() { bindgen_imported_type(vis, t, tokens); } @@ -510,3 +513,40 @@ fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) { invocation.to_tokens(tokens); } } + +fn bindgen_enum(e: &ast::Enum, into: &mut Tokens) { + let enum_name = &e.name; + let c = shared::TYPE_ENUM as u32; + let incoming_u32 = quote! { n }; + let enum_name_as_string = enum_name.to_string(); + let cast_clauses = e.variants.iter().map(|variant| { + let &(variant_name, _) = variant; + quote! { + if #incoming_u32 == #enum_name::#variant_name as u32 { + #enum_name::#variant_name + } + } + }); + (my_quote! { + impl #enum_name { + fn from_u32(#incoming_u32: u32) -> #enum_name { + #(#cast_clauses else)* { + wasm_bindgen::throw(&format!("Could not cast {} as {}", #incoming_u32, #enum_name_as_string)); + } + } + } + + impl ::wasm_bindgen::convert::WasmBoundary for #enum_name { + type Js = u32; + const DESCRIPTOR: u32 = #c; + + fn into_js(self) -> u32 { + self as u32 + } + + unsafe fn from_js(js: u32) -> Self { + #enum_name::from_u32(js) + } + } + }).to_tokens(into); +} diff --git a/crates/wasm-bindgen-shared/src/lib.rs b/crates/wasm-bindgen-shared/src/lib.rs index 7fe56b5d..62d0d220 100644 --- a/crates/wasm-bindgen-shared/src/lib.rs +++ b/crates/wasm-bindgen-shared/src/lib.rs @@ -8,6 +8,7 @@ use std::hash::{Hash, Hasher}; #[derive(Deserialize)] pub struct Program { pub exports: Vec, + pub enums: Vec, pub imports: Vec, pub custom_type_names: Vec, } @@ -32,6 +33,18 @@ pub struct Export { pub function: Function, } +#[derive(Deserialize)] +pub struct Enum { + pub name: String, + pub variants: Vec, +} + +#[derive(Deserialize)] +pub struct EnumVariant { + pub name: String, + pub value: u32 +} + #[derive(Deserialize)] pub struct Function { pub name: String, @@ -77,6 +90,7 @@ pub fn mangled_import_name(struct_: Option<&str>, f: &str) -> String { pub type Type = char; +pub const TYPE_ENUM: char = '\u{5d}'; pub const TYPE_NUMBER: char = '\u{5e}'; pub const TYPE_BORROWED_STR: char = '\u{5f}'; pub const TYPE_STRING: char = '\u{60}'; diff --git a/tests/enums.rs b/tests/enums.rs new file mode 100644 index 00000000..26b3646c --- /dev/null +++ b/tests/enums.rs @@ -0,0 +1,87 @@ +extern crate test_support; + +#[test] +fn c_style_enum() { + test_support::project() + .file("src/lib.rs", r#" + #![feature(proc_macro)] + + extern crate wasm_bindgen; + + use wasm_bindgen::prelude::*; + + #[wasm_bindgen] + pub enum Color { + Green, + Yellow, + Red, + } + + #[no_mangle] + #[wasm_bindgen] + pub extern fn cycle(color: Color) -> Color { + match color { + Color::Green => Color::Yellow, + Color::Yellow => Color::Red, + Color::Red => Color::Green, + } + } + "#) + .file("test.ts", r#" + import * as assert from "assert"; + import * as wasm from "./out"; + + export function test() { + assert.strictEqual(wasm.Color.Green, 0); + assert.strictEqual(wasm.Color.Yellow, 1); + assert.strictEqual(wasm.Color.Red, 2); + assert.strictEqual(Object.keys(wasm.Color).length, 3); + + assert.strictEqual(wasm.cycle(wasm.Color.Green), wasm.Color.Yellow); + } + "#) + .test(); +} + +#[test] +fn c_style_enum_with_custom_values() { + test_support::project() + .file("src/lib.rs", r#" + #![feature(proc_macro)] + + extern crate wasm_bindgen; + + use wasm_bindgen::prelude::*; + + #[wasm_bindgen] + pub enum Color { + Green = 21, + Yellow = 34, + Red, + } + + #[no_mangle] + #[wasm_bindgen] + pub extern fn cycle(color: Color) -> Color { + match color { + Color::Green => Color::Yellow, + Color::Yellow => Color::Red, + Color::Red => Color::Green, + } + } + "#) + .file("test.ts", r#" + import * as assert from "assert"; + import * as wasm from "./out"; + + export function test() { + assert.strictEqual(wasm.Color.Green, 21); + assert.strictEqual(wasm.Color.Yellow, 34); + assert.strictEqual(wasm.Color.Red, 2); + assert.strictEqual(Object.keys(wasm.Color).length, 3); + + assert.strictEqual(wasm.cycle(wasm.Color.Green), wasm.Color.Yellow); + } + "#) + .test(); +}