Skip to content

C-Style Enums #36

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Feb 26, 2018
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion crates/wasm-bindgen-cli-support/src/js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it not possible to expose actual enum name instead of generic number?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is. It requires a bit of a refactoring of how things are handled though. Specifically we lose all information about enums inside of functions right now, and we'll have to refactor how we handle "custom types" to make it work. I would prefer to save it for the next PR where I'll handle non-C-style enums. The changes necessary for that will sure this up.

dst_ts.push_str(": number");
if self.cx.config.debug {
self.cx.expose_assert_num();
Expand Down Expand Up @@ -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;")
Expand Down Expand Up @@ -1423,6 +1430,27 @@ 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();

let mut value = 0;
for variant in enum_.variants.iter() {
variants.push_str(&format!("{}:{},", variant.name, value));
value = value + 1;
}
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 {
Expand Down
44 changes: 43 additions & 1 deletion crates/wasm-bindgen-macro/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use syn;
pub struct Program {
pub exports: Vec<Export>,
pub imports: Vec<Import>,
pub enums: Vec<Enum>,
pub imported_types: Vec<(syn::Visibility, syn::Ident)>,
pub structs: Vec<Struct>,
}
Expand Down Expand Up @@ -47,6 +48,11 @@ pub struct Struct {
pub name: syn::Ident,
}

pub struct Enum {
pub name: syn::Ident,
pub variants: Vec<syn::Ident>
}

pub enum Type {
// special
Vector(VectorType, bool),
Expand Down Expand Up @@ -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"),
}
}

Expand Down Expand Up @@ -195,6 +206,25 @@ 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().map(|ref v| {
match v.fields {
syn::Fields::Unit => (),
_ => panic!("Only C-Style enums allowed")
}
v.ident
}).collect();
self.enums.push(Enum {
name: item.ident,
variants
});
}

pub fn push_foreign_fn(&mut self,
mut f: syn::ForeignItemFn,
module_opts: &BindgenAttrs) {
Expand Down Expand Up @@ -297,6 +327,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)
Expand Down Expand Up @@ -634,6 +665,17 @@ 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| {
a.fields(&[("name", &|a| a.str(v.as_ref()))])
})),
]);
}
}

impl Struct {
fn from(s: syn::ItemStruct, _opts: BindgenAttrs) -> Struct {
Struct { name: s.ident }
Expand Down
39 changes: 39 additions & 0 deletions crates/wasm-bindgen-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -510,3 +513,39 @@ fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) {
invocation.to_tokens(tokens);
}
}

fn bindgen_enum(e: &ast::Enum, into: &mut Tokens) {
let name = &e.name;
let c = shared::TYPE_ENUM as u32;
let incoming_u32 = quote! { n };
let name_as_string = name.to_string();
let cast_clauses = e.variants.iter().map(|ident| {
quote! {
if #incoming_u32 == #name::#ident as u32 {
#name::#ident
}
}
});
(my_quote! {
impl #name {
fn from_u32(#incoming_u32: u32) -> #name {
#(#cast_clauses else)* {
wasm_bindgen::throw(&format!("Could not cast {} as {}", #incoming_u32, #name_as_string));
}
}
}

impl ::wasm_bindgen::convert::WasmBoundary for #name {
type Js = u32;
const DESCRIPTOR: u32 = #c;

fn into_js(self) -> u32 {
self as u32
}

unsafe fn from_js(js: u32) -> Self {
#name::from_u32(js)
}
}
}).to_tokens(into);
}
13 changes: 13 additions & 0 deletions crates/wasm-bindgen-shared/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::hash::{Hash, Hasher};
#[derive(Deserialize)]
pub struct Program {
pub exports: Vec<Export>,
pub enums: Vec<Enum>,
pub imports: Vec<Import>,
pub custom_type_names: Vec<CustomTypeName>,
}
Expand All @@ -32,6 +33,17 @@ pub struct Export {
pub function: Function,
}

#[derive(Deserialize)]
pub struct Enum {
pub name: String,
pub variants: Vec<EnumVariant>,
}

#[derive(Deserialize)]
pub struct EnumVariant {
pub name: String
}

#[derive(Deserialize)]
pub struct Function {
pub name: String,
Expand Down Expand Up @@ -77,6 +89,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}';
Expand Down
44 changes: 44 additions & 0 deletions tests/enums.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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();
}