WebIDL: Handle Invalid Enum Returns (#477)

* move ImportEnum attributes to a property

* borrow from_js_value argument

* make WebIDL enums non-exhaustive

* add more tests for WebIDL enums
This commit is contained in:
Stephan Wolski 2018-07-23 11:04:28 -04:00 committed by Alex Crichton
parent 5fddcf3868
commit b3ee71c20b
4 changed files with 144 additions and 20 deletions

View File

@ -112,6 +112,8 @@ pub struct ImportEnum {
pub variants: Vec<Ident>,
/// The JS string values of the variants
pub variant_values: Vec<String>,
/// Attributes to apply to the Rust enum
pub rust_attrs: Vec<syn::Attribute>,
}
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]

View File

@ -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<Literal> = 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),
}
}
}

View File

@ -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)])],
}),
});

View File

@ -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();
}