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 all 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
28 changes: 27 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 => {
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,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 {
Expand Down
57 changes: 56 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, u32)>
}

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,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() > <u32>::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) {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 }
Expand Down
40 changes: 40 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,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);
}
14 changes: 14 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,18 @@ 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,
pub value: u32
}

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