From a981dfd507e27a41f209dea2c66abed8ffcae5d1 Mon Sep 17 00:00:00 2001 From: Stephan Wolski Date: Sun, 8 Jul 2018 22:09:00 -0400 Subject: [PATCH 1/5] webidl: initial enum support Add enum support to the WebIDL interface generator. --- crates/backend/src/ast.rs | 21 ++++++ crates/backend/src/codegen.rs | 96 +++++++++++++++++++++++++++- crates/backend/src/defined.rs | 10 +++ crates/cli-support/src/js/js2rust.rs | 12 ++-- crates/cli-support/src/js/mod.rs | 1 + crates/cli-support/src/js/rust2js.rs | 9 ++- crates/shared/src/lib.rs | 4 ++ crates/webidl/src/lib.rs | 33 +++++++++- crates/webidl/tests/all/enums.rs | 64 +++++++++++++++++++ crates/webidl/tests/all/main.rs | 1 + 10 files changed, 239 insertions(+), 12 deletions(-) create mode 100644 crates/webidl/tests/all/enums.rs 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..9d65e0f00 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,98 @@ 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 name_string = &self.name.to_string(); + 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 ::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 { + match self { + #(#variant_paths_ref => ::wasm_bindgen::JsValue::from_str(#variant_strings).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(::wasm_bindgen::JsValue::from_abi(js, extra)) + } + } + + impl From<::wasm_bindgen::JsValue> for #name { + fn from(obj: ::wasm_bindgen::JsValue) -> #name { + let obj_str = match obj.as_string() { + Some(string_value) => string_value, + None => panic!("Can't convert a non-string into {}", #name_string), + }; + + match obj_str.as_str() { + #(#variant_strings => #variant_paths_ref,)* + unknown_value => panic!("Can't convert \"{}\" into {}", unknown_value, #name_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 +848,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 12469d150..7fa546a44 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(..) @@ -236,7 +237,8 @@ impl<'a> WebidlParse<&'a webidl::ast::NonPartialInterface> for webidl::ast::Exte match self { webidl::ast::ExtendedAttribute::ArgumentList( webidl::ast::ArgumentListExtendedAttribute { arguments, name }, - ) if name == "Constructor" => + ) + if name == "Constructor" => { add_constructor(arguments, &interface.name); } @@ -251,7 +253,8 @@ impl<'a> WebidlParse<&'a webidl::ast::NonPartialInterface> for webidl::ast::Exte rhs_arguments, rhs_name, }, - ) if lhs_name == "NamedConstructor" => + ) + if lhs_name == "NamedConstructor" => { add_constructor(rhs_arguments, rhs_name); } @@ -396,3 +399,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..84b158847 --- /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); + let square = Shape::new(ShapeType::Square); + 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 dffe6eccd..fd6a36948 100644 --- a/crates/webidl/tests/all/main.rs +++ b/crates/webidl/tests/all/main.rs @@ -2,3 +2,4 @@ extern crate wasm_bindgen_test_project_builder as project_builder; use project_builder::project; mod simple; +mod enums; From c56b1b0189507717b7e0dec9a915db8b2f289bf1 Mon Sep 17 00:00:00 2001 From: Stephan Wolski Date: Tue, 10 Jul 2018 20:19:06 -0400 Subject: [PATCH 2/5] webidl: replace From trait with from_js_value method for enums * Remove From for ENUM * Add `from_js_value` method which returns an Option --- crates/backend/src/codegen.rs | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 9d65e0f00..06983a165 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -591,7 +591,7 @@ impl ToTokens for ast::ImportEnum { fn to_tokens(&self, tokens: &mut TokenStream) { let vis = &self.vis; let name = &self.name; - let name_string = &self.name.to_string(); + let expect_string = format!("attempted to convert invalid JSValue into {}", name); let variants = &self.variants; let variant_strings = &self.variant_values; @@ -625,6 +625,15 @@ impl ToTokens for ast::ImportEnum { #(#variants = #variant_indexes_ref,)* } + impl #name { + 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() @@ -650,21 +659,7 @@ impl ToTokens for ast::ImportEnum { js: Self::Abi, extra: &mut ::wasm_bindgen::convert::Stack, ) -> Self { - #name::from(::wasm_bindgen::JsValue::from_abi(js, extra)) - } - } - - impl From<::wasm_bindgen::JsValue> for #name { - fn from(obj: ::wasm_bindgen::JsValue) -> #name { - let obj_str = match obj.as_string() { - Some(string_value) => string_value, - None => panic!("Can't convert a non-string into {}", #name_string), - }; - - match obj_str.as_str() { - #(#variant_strings => #variant_paths_ref,)* - unknown_value => panic!("Can't convert \"{}\" into {}", unknown_value, #name_string), - } + #name::from_js_value(::wasm_bindgen::JsValue::from_abi(js, extra)).expect(#expect_string) } } From 6608828d7261b7cef95b124179043b6bdcf62767 Mon Sep 17 00:00:00 2001 From: Stephan Wolski Date: Tue, 10 Jul 2018 20:23:09 -0400 Subject: [PATCH 3/5] webidl: use JSValue::from in `IntoWasmAbi` for enums --- crates/backend/src/codegen.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 06983a165..65f3476fe 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -645,9 +645,7 @@ impl ToTokens for ast::ImportEnum { ::wasm_bindgen::convert::IntoWasmAbi>::Abi; fn into_abi(self, extra: &mut ::wasm_bindgen::convert::Stack) -> Self::Abi { - match self { - #(#variant_paths_ref => ::wasm_bindgen::JsValue::from_str(#variant_strings).into_abi(extra),)* - } + ::wasm_bindgen::JsValue::from(self).into_abi(extra) } } From fac73a2ea4b2f859b37d5dbff1e223860e6c09bc Mon Sep 17 00:00:00 2001 From: Stephan Wolski Date: Wed, 11 Jul 2018 15:36:41 -0400 Subject: [PATCH 4/5] webidl: make from_js_value visible for enums Make from_js_value match the visibility of the enum it's associated with --- crates/backend/src/codegen.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 65f3476fe..1e3e69af7 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -626,7 +626,7 @@ impl ToTokens for ast::ImportEnum { } impl #name { - fn from_js_value(obj: ::wasm_bindgen::JsValue) -> Option<#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, From 5520da1ef692f837f11d6237bbe2d5199f6b5338 Mon Sep 17 00:00:00 2001 From: Stephan Wolski Date: Wed, 11 Jul 2018 16:17:56 -0400 Subject: [PATCH 5/5] webidl: update enum test to unwrap constructor results Update the enum test to match the new constructor return values --- crates/webidl/tests/all/enums.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/webidl/tests/all/enums.rs b/crates/webidl/tests/all/enums.rs index 84b158847..6d3ac6d2a 100644 --- a/crates/webidl/tests/all/enums.rs +++ b/crates/webidl/tests/all/enums.rs @@ -51,8 +51,8 @@ fn top_level_enum() { #[wasm_bindgen] pub fn test() { - let circle = Shape::new(ShapeType::Circle); - let square = Shape::new(ShapeType::Square); + 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());