diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 87f5590e7..2c3c9661b 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -41,6 +41,7 @@ pub enum ImportKind { Function(ImportFunction), Static(ImportStatic), Type(ImportType), + Enum(ImportEnum), } #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] @@ -99,6 +100,18 @@ pub struct ImportType { pub attrs: Vec, } +#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] +pub struct ImportEnum { + /// The Rust enum's visibility + pub vis: syn::Visibility, + /// The Rust enum's identifiers + pub name: Ident, + /// The Rust identifiers for the variants + pub variants: Vec, + /// The JS string values of the variants + pub variant_values: Vec, +} + #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] pub struct Function { pub name: Ident, @@ -279,6 +292,7 @@ impl ImportKind { ImportKind::Function(_) => true, ImportKind::Static(_) => false, ImportKind::Type(_) => false, + ImportKind::Enum(_) => false, } } @@ -287,6 +301,7 @@ impl ImportKind { ImportKind::Function(ref f) => shared::ImportKind::Function(f.shared()), ImportKind::Static(ref f) => shared::ImportKind::Static(f.shared()), ImportKind::Type(ref f) => shared::ImportKind::Type(f.shared()), + ImportKind::Enum(ref f) => shared::ImportKind::Enum(f.shared()), } } } @@ -364,6 +379,12 @@ impl ImportType { } } +impl ImportEnum { + fn shared(&self) -> shared::ImportEnum { + shared::ImportEnum {} + } +} + impl Struct { fn shared(&self) -> shared::Struct { shared::Struct { diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 654b11bba..1e3e69af7 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -4,7 +4,7 @@ use std::env; use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT}; use ast; -use proc_macro2::{Ident, Span, TokenStream}; +use proc_macro2::{Ident, Literal, Span, TokenStream}; use quote::ToTokens; use serde_json; use shared; @@ -500,6 +500,7 @@ impl ToTokens for ast::ImportKind { ast::ImportKind::Function(ref f) => f.to_tokens(tokens), ast::ImportKind::Static(ref s) => s.to_tokens(tokens), ast::ImportKind::Type(ref t) => t.to_tokens(tokens), + ast::ImportKind::Enum(ref e) => e.to_tokens(tokens), } } } @@ -586,6 +587,91 @@ impl ToTokens for ast::ImportType { } } +impl ToTokens for ast::ImportEnum { + fn to_tokens(&self, tokens: &mut TokenStream) { + let vis = &self.vis; + let name = &self.name; + let expect_string = format!("attempted to convert invalid JSValue into {}", name); + let variants = &self.variants; + let variant_strings = &self.variant_values; + + let mut current_idx: usize = 0; + let variant_indexes: Vec = variants + .iter() + .map(|_| { + let this_index = current_idx; + current_idx += 1; + Literal::usize_unsuffixed(this_index) + }) + .collect(); + + // Borrow variant_indexes because we need to use it multiple times inside the quote! macro + let variant_indexes_ref = &variant_indexes; + + // A vector of EnumName::VariantName tokens for this enum + let variant_paths: Vec = self + .variants + .iter() + .map(|v| quote!(#name::#v).into_token_stream()) + .collect(); + + // Borrow variant_paths because we need to use it multiple times inside the quote! macro + let variant_paths_ref = &variant_paths; + + (quote! { + #[allow(bad_style)] + #[derive(Copy, Clone, Debug)] + #vis enum #name { + #(#variants = #variant_indexes_ref,)* + } + + impl #name { + #vis fn from_js_value(obj: ::wasm_bindgen::JsValue) -> Option<#name> { + obj.as_string().and_then(|obj_str| match obj_str.as_str() { + #(#variant_strings => Some(#variant_paths_ref),)* + _ => None, + }) + } + } + + impl ::wasm_bindgen::describe::WasmDescribe for #name { + fn describe() { + ::wasm_bindgen::JsValue::describe() + } + } + + impl ::wasm_bindgen::convert::IntoWasmAbi for #name { + type Abi = <::wasm_bindgen::JsValue as + ::wasm_bindgen::convert::IntoWasmAbi>::Abi; + + fn into_abi(self, extra: &mut ::wasm_bindgen::convert::Stack) -> Self::Abi { + ::wasm_bindgen::JsValue::from(self).into_abi(extra) + } + } + + impl ::wasm_bindgen::convert::FromWasmAbi for #name { + type Abi = <::wasm_bindgen::JsValue as + ::wasm_bindgen::convert::FromWasmAbi>::Abi; + + unsafe fn from_abi( + js: Self::Abi, + extra: &mut ::wasm_bindgen::convert::Stack, + ) -> Self { + #name::from_js_value(::wasm_bindgen::JsValue::from_abi(js, extra)).expect(#expect_string) + } + } + + impl From<#name> for ::wasm_bindgen::JsValue { + fn from(obj: #name) -> ::wasm_bindgen::JsValue { + match obj { + #(#variant_paths_ref => ::wasm_bindgen::JsValue::from_str(#variant_strings)),* + } + } + } + }).to_tokens(tokens); + } +} + impl ToTokens for ast::ImportFunction { fn to_tokens(&self, tokens: &mut TokenStream) { let mut class_ty = None; @@ -755,6 +841,7 @@ impl<'a> ToTokens for DescribeImport<'a> { ast::ImportKind::Function(ref f) => f, ast::ImportKind::Static(_) => return, ast::ImportKind::Type(_) => return, + ast::ImportKind::Enum(_) => return, }; let describe_name = format!("__wbindgen_describe_{}", f.shim); let describe_name = Ident::new(&describe_name, Span::call_site()); diff --git a/crates/backend/src/defined.rs b/crates/backend/src/defined.rs index 7ca803769..d775cb02f 100644 --- a/crates/backend/src/defined.rs +++ b/crates/backend/src/defined.rs @@ -105,6 +105,7 @@ impl ImportedTypes for ast::ImportKind { ast::ImportKind::Static(s) => s.imported_types(f), ast::ImportKind::Function(fun) => fun.imported_types(f), ast::ImportKind::Type(ty) => ty.imported_types(f), + ast::ImportKind::Enum(enm) => enm.imported_types(f), } } } @@ -210,6 +211,15 @@ impl ImportedTypes for ast::ImportType { } } +impl ImportedTypes for ast::ImportEnum { + fn imported_types(&self, f: &mut F) + where + F: FnMut(&Ident, ImportedTypeKind), + { + f(&self.name, ImportedTypeKind::Definition); + } +} + impl ImportedTypes for ast::TypeAlias { fn imported_types(&self, f: &mut F) where diff --git a/crates/cli-support/src/js/js2rust.rs b/crates/cli-support/src/js/js2rust.rs index 5664209e4..c16d7511b 100644 --- a/crates/cli-support/src/js/js2rust.rs +++ b/crates/cli-support/src/js/js2rust.rs @@ -71,13 +71,15 @@ impl<'a, 'b> Js2Rust<'a, 'b> { self.prelude( "if (this.ptr === 0) { throw new Error('Attempt to use a moved value'); - }" + }", ); if consumed { - self.prelude("\ - const ptr = this.ptr;\n\ - this.ptr = 0;\n\ - "); + self.prelude( + "\ + const ptr = this.ptr;\n\ + this.ptr = 0;\n\ + ", + ); self.rust_arguments.insert(0, "ptr".to_string()); } else { self.rust_arguments.insert(0, "this.ptr".to_string()); diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index ca8ce2336..ef47aa55b 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1756,6 +1756,7 @@ impl<'a, 'b> SubContext<'a, 'b> { })?; } shared::ImportKind::Type(_) => {} + shared::ImportKind::Enum(_) => {} } Ok(()) } diff --git a/crates/cli-support/src/js/rust2js.rs b/crates/cli-support/src/js/rust2js.rs index ec9ad937e..be8f8b90c 100644 --- a/crates/cli-support/src/js/rust2js.rs +++ b/crates/cli-support/src/js/rust2js.rs @@ -306,7 +306,8 @@ impl<'a, 'b> Rust2Js<'a, 'b> { // Insert an assertion to the type of the returned value as // otherwise this will cause memory unsafety on the Rust side of // things. - self.ret_expr = format!("\ + self.ret_expr = format!( + "\ const val = JS; if (!(val instanceof {0})) {{ throw new Error('expected value of type {0}'); @@ -314,8 +315,10 @@ impl<'a, 'b> Rust2Js<'a, 'b> { const ret = val.ptr; val.ptr = 0; return ret;\ - ", class); - return Ok(()) + ", + class + ); + return Ok(()); } self.ret_expr = match *ty { diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 082850923..8df1e5344 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -33,6 +33,7 @@ pub enum ImportKind { Function(ImportFunction), Static(ImportStatic), Type(ImportType), + Enum(ImportEnum), } #[derive(Deserialize, Serialize)] @@ -78,6 +79,9 @@ pub struct ImportStatic { #[derive(Deserialize, Serialize)] pub struct ImportType {} +#[derive(Deserialize, Serialize)] +pub struct ImportEnum {} + #[derive(Deserialize, Serialize)] pub struct Export { pub class: Option, diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index c9678b944..e54e08bb9 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -29,6 +29,7 @@ use std::path::Path; use backend::defined::{ImportedTypeDefinitions, RemoveUndefinedImports}; use backend::util::{ident_ty, rust_ident, wrap_import_function}; use failure::ResultExt; +use heck::CamelCase; use quote::ToTokens; use util::{ @@ -111,10 +112,10 @@ impl WebidlParse<()> for webidl::ast::Definition { interface.webidl_parse(program, ()) } webidl::ast::Definition::Typedef(ref typedef) => typedef.webidl_parse(program, ()), + webidl::ast::Definition::Enum(ref enumeration) => enumeration.webidl_parse(program, ()), // TODO webidl::ast::Definition::Callback(..) | webidl::ast::Definition::Dictionary(..) - | webidl::ast::Definition::Enum(..) | webidl::ast::Definition::Implements(..) | webidl::ast::Definition::Includes(..) | webidl::ast::Definition::Mixin(..) @@ -449,3 +450,27 @@ impl<'a> WebidlParse<&'a str> for webidl::ast::StaticOperation { Ok(()) } } + +impl<'a> WebidlParse<()> for webidl::ast::Enum { + fn webidl_parse(&self, program: &mut backend::ast::Program, _: ()) -> Result<()> { + program.imports.push(backend::ast::Import { + module: None, + version: None, + js_namespace: None, + kind: backend::ast::ImportKind::Enum(backend::ast::ImportEnum { + vis: syn::Visibility::Public(syn::VisPublic { + pub_token: Default::default(), + }), + name: rust_ident(self.name.to_camel_case().as_str()), + variants: self + .variants + .iter() + .map(|v| rust_ident(v.to_camel_case().as_str())) + .collect(), + variant_values: self.variants.clone(), + }), + }); + + Ok(()) + } +} diff --git a/crates/webidl/tests/all/enums.rs b/crates/webidl/tests/all/enums.rs new file mode 100644 index 000000000..6d3ac6d2a --- /dev/null +++ b/crates/webidl/tests/all/enums.rs @@ -0,0 +1,64 @@ +use super::project; + +#[test] +fn top_level_enum() { + project() + .file( + "shape.webidl", + r#" + enum ShapeType { "circle", "square" }; + + [Constructor(ShapeType kind)] + interface Shape { + [Pure] + boolean isSquare(); + + [Pure] + boolean isCircle(); + }; + "#, + ) + .file( + "shape.mjs", + r#" + export class Shape { + constructor(kind) { + this.kind = kind; + } + + isSquare() { + return this.kind === 'square'; + } + + isCircle() { + return this.kind === 'circle'; + } + } + "#, + ) + .file( + "src/lib.rs", + r#" + #![feature(proc_macro, wasm_custom_section, wasm_import_module)] + + extern crate wasm_bindgen; + + use wasm_bindgen::prelude::*; + + pub mod shape; + + use shape::{Shape, ShapeType}; + + #[wasm_bindgen] + pub fn test() { + let circle = Shape::new(ShapeType::Circle).unwrap(); + let square = Shape::new(ShapeType::Square).unwrap(); + assert!(circle.is_circle()); + assert!(!circle.is_square()); + assert!(square.is_square()); + assert!(!square.is_circle()); + } + "#, + ) + .test(); +} diff --git a/crates/webidl/tests/all/main.rs b/crates/webidl/tests/all/main.rs index 6cce8f3db..13a6f7890 100644 --- a/crates/webidl/tests/all/main.rs +++ b/crates/webidl/tests/all/main.rs @@ -2,4 +2,5 @@ extern crate wasm_bindgen_test_project_builder as project_builder; use project_builder::project; mod simple; +mod enums; mod throws;