diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 8d54083c..34cdd980 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -112,6 +112,8 @@ pub struct ImportEnum { pub variants: Vec, /// The JS string values of the variants pub variant_values: Vec, + /// Attributes to apply to the Rust enum + pub rust_attrs: Vec, } #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 78094d2a..b0aa321f 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -580,9 +580,10 @@ 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 expect_string = format!("attempted to convert invalid {} into JSValue", name); let variants = &self.variants; let variant_strings = &self.variant_values; + let attrs = &self.rust_attrs; let mut current_idx: usize = 0; let variant_indexes: Vec = variants @@ -609,13 +610,15 @@ impl ToTokens for ast::ImportEnum { (quote! { #[allow(bad_style)] - #[derive(Copy, Clone, Debug)] + #(#attrs)* #vis enum #name { #(#variants = #variant_indexes_ref,)* + #[doc(hidden)] + __Nonexhaustive, } impl #name { - #vis 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, @@ -646,14 +649,15 @@ impl ToTokens for ast::ImportEnum { js: Self::Abi, extra: &mut ::wasm_bindgen::convert::Stack, ) -> Self { - #name::from_js_value(::wasm_bindgen::JsValue::from_abi(js, extra)).expect(#expect_string) + #name::from_js_value(&::wasm_bindgen::JsValue::from_abi(js, extra)).unwrap_or(#name::__Nonexhaustive) } } 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)),* + #(#variant_paths_ref => ::wasm_bindgen::JsValue::from_str(#variant_strings),)* + #name::__Nonexhaustive => panic!(#expect_string), } } } diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index d1acf28e..cfdaa3b5 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -14,7 +14,9 @@ extern crate heck; #[macro_use] extern crate log; extern crate proc_macro2; +#[macro_use] extern crate quote; +#[macro_use] extern crate syn; extern crate wasm_bindgen_backend as backend; extern crate webidl; @@ -640,6 +642,7 @@ impl<'a> WebidlParse<()> for webidl::ast::Enum { .map(|v| rust_ident(v.to_camel_case().as_str())) .collect(), variant_values: self.variants.clone(), + rust_attrs: vec![parse_quote!(#[derive(Copy, Clone, PartialEq, Debug)])], }), }); diff --git a/crates/webidl/tests/all/enums.rs b/crates/webidl/tests/all/enums.rs index 8af31dd5..415dda7c 100644 --- a/crates/webidl/tests/all/enums.rs +++ b/crates/webidl/tests/all/enums.rs @@ -1,23 +1,25 @@ use super::project; +static SHAPE_IDL: &'static str = r#" + enum ShapeType { "circle", "square" }; + + [Constructor(ShapeType kind)] + interface Shape { + [Pure] + boolean isSquare(); + + [Pure] + boolean isCircle(); + + [Pure] + ShapeType getShape(); + }; +"#; + #[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.webidl", SHAPE_IDL) .file( "shape.mjs", r#" @@ -33,6 +35,10 @@ fn top_level_enum() { isCircle() { return this.kind === 'circle'; } + + getShape() { + return this.kind; + } } "#, ) @@ -62,3 +68,112 @@ fn top_level_enum() { ) .test(); } + +#[test] +fn valid_enum_return() { + project() + .file("shape.webidl", SHAPE_IDL) + .file( + "shape.mjs", + r#" + export class Shape { + constructor(kind) { + this.kind = kind; + } + + isSquare() { + return this.kind === 'square'; + } + + isCircle() { + return this.kind === 'circle'; + } + + getShape() { + return this.kind; + } + } + "#, + ) + .file( + "src/lib.rs", + r#" + #![feature(use_extern_macros)] + + 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_eq!(circle.get_shape(), ShapeType::Circle); + assert!(square.is_square()); + assert!(!square.is_circle()); + assert_eq!(square.get_shape(), ShapeType::Square); + } + "#, + ) + .test(); +} + +#[test] +fn invalid_enum_return() { + project() + .file("shape.webidl", SHAPE_IDL) + .file( + "shape.mjs", + r#" + export class Shape { + constructor(kind) { + this.kind = 'triangle'; // <-- invalid ShapeType + } + + isSquare() { + return this.kind === 'square'; + } + + isCircle() { + return this.kind === 'circle'; + } + + getShape() { + return this.kind; + } + } + "#, + ) + .file( + "src/lib.rs", + r#" + #![feature(use_extern_macros)] + + extern crate wasm_bindgen; + + use wasm_bindgen::prelude::*; + + pub mod shape; + + use shape::{Shape, ShapeType}; + + #[wasm_bindgen] + pub fn test() { + let actually_a_triangle = Shape::new(ShapeType::Circle).unwrap(); + assert!(!actually_a_triangle.is_circle()); + assert!(!actually_a_triangle.is_square()); + match actually_a_triangle.get_shape() { + ShapeType::Circle | ShapeType::Square => assert!(false), + _ => {} // Success + }; + } + "#, + ) + .test(); +}