Skip to content

Commit b3ee71c

Browse files
Slowkialexcrichton
authored andcommitted
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
1 parent 5fddcf3 commit b3ee71c

File tree

4 files changed

+140
-16
lines changed

4 files changed

+140
-16
lines changed

crates/backend/src/ast.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ pub struct ImportEnum {
112112
pub variants: Vec<Ident>,
113113
/// The JS string values of the variants
114114
pub variant_values: Vec<String>,
115+
/// Attributes to apply to the Rust enum
116+
pub rust_attrs: Vec<syn::Attribute>,
115117
}
116118

117119
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]

crates/backend/src/codegen.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -580,9 +580,10 @@ impl ToTokens for ast::ImportEnum {
580580
fn to_tokens(&self, tokens: &mut TokenStream) {
581581
let vis = &self.vis;
582582
let name = &self.name;
583-
let expect_string = format!("attempted to convert invalid JSValue into {}", name);
583+
let expect_string = format!("attempted to convert invalid {} into JSValue", name);
584584
let variants = &self.variants;
585585
let variant_strings = &self.variant_values;
586+
let attrs = &self.rust_attrs;
586587

587588
let mut current_idx: usize = 0;
588589
let variant_indexes: Vec<Literal> = variants
@@ -609,13 +610,15 @@ impl ToTokens for ast::ImportEnum {
609610

610611
(quote! {
611612
#[allow(bad_style)]
612-
#[derive(Copy, Clone, Debug)]
613+
#(#attrs)*
613614
#vis enum #name {
614615
#(#variants = #variant_indexes_ref,)*
616+
#[doc(hidden)]
617+
__Nonexhaustive,
615618
}
616619

617620
impl #name {
618-
#vis fn from_js_value(obj: ::wasm_bindgen::JsValue) -> Option<#name> {
621+
#vis fn from_js_value(obj: &::wasm_bindgen::JsValue) -> Option<#name> {
619622
obj.as_string().and_then(|obj_str| match obj_str.as_str() {
620623
#(#variant_strings => Some(#variant_paths_ref),)*
621624
_ => None,
@@ -646,14 +649,15 @@ impl ToTokens for ast::ImportEnum {
646649
js: Self::Abi,
647650
extra: &mut ::wasm_bindgen::convert::Stack,
648651
) -> Self {
649-
#name::from_js_value(::wasm_bindgen::JsValue::from_abi(js, extra)).expect(#expect_string)
652+
#name::from_js_value(&::wasm_bindgen::JsValue::from_abi(js, extra)).unwrap_or(#name::__Nonexhaustive)
650653
}
651654
}
652655

653656
impl From<#name> for ::wasm_bindgen::JsValue {
654657
fn from(obj: #name) -> ::wasm_bindgen::JsValue {
655658
match obj {
656-
#(#variant_paths_ref => ::wasm_bindgen::JsValue::from_str(#variant_strings)),*
659+
#(#variant_paths_ref => ::wasm_bindgen::JsValue::from_str(#variant_strings),)*
660+
#name::__Nonexhaustive => panic!(#expect_string),
657661
}
658662
}
659663
}

crates/webidl/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ extern crate heck;
1414
#[macro_use]
1515
extern crate log;
1616
extern crate proc_macro2;
17+
#[macro_use]
1718
extern crate quote;
19+
#[macro_use]
1820
extern crate syn;
1921
extern crate wasm_bindgen_backend as backend;
2022
extern crate webidl;
@@ -640,6 +642,7 @@ impl<'a> WebidlParse<()> for webidl::ast::Enum {
640642
.map(|v| rust_ident(v.to_camel_case().as_str()))
641643
.collect(),
642644
variant_values: self.variants.clone(),
645+
rust_attrs: vec![parse_quote!(#[derive(Copy, Clone, PartialEq, Debug)])],
643646
}),
644647
});
645648

crates/webidl/tests/all/enums.rs

Lines changed: 126 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,78 @@
11
use super::project;
22

3+
static SHAPE_IDL: &'static str = r#"
4+
enum ShapeType { "circle", "square" };
5+
6+
[Constructor(ShapeType kind)]
7+
interface Shape {
8+
[Pure]
9+
boolean isSquare();
10+
11+
[Pure]
12+
boolean isCircle();
13+
14+
[Pure]
15+
ShapeType getShape();
16+
};
17+
"#;
18+
319
#[test]
420
fn top_level_enum() {
521
project()
22+
.file("shape.webidl", SHAPE_IDL)
23+
.file(
24+
"shape.mjs",
25+
r#"
26+
export class Shape {
27+
constructor(kind) {
28+
this.kind = kind;
29+
}
30+
31+
isSquare() {
32+
return this.kind === 'square';
33+
}
34+
35+
isCircle() {
36+
return this.kind === 'circle';
37+
}
38+
39+
getShape() {
40+
return this.kind;
41+
}
42+
}
43+
"#,
44+
)
645
.file(
7-
"shape.webidl",
46+
"src/lib.rs",
847
r#"
9-
enum ShapeType { "circle", "square" };
10-
11-
[Constructor(ShapeType kind)]
12-
interface Shape {
13-
[Pure]
14-
boolean isSquare();
15-
16-
[Pure]
17-
boolean isCircle();
18-
};
48+
#![feature(use_extern_macros)]
49+
50+
extern crate wasm_bindgen;
51+
52+
use wasm_bindgen::prelude::*;
53+
54+
pub mod shape;
55+
56+
use shape::{Shape, ShapeType};
57+
58+
#[wasm_bindgen]
59+
pub fn test() {
60+
let circle = Shape::new(ShapeType::Circle).unwrap();
61+
let square = Shape::new(ShapeType::Square).unwrap();
62+
assert!(circle.is_circle());
63+
assert!(!circle.is_square());
64+
assert!(square.is_square());
65+
assert!(!square.is_circle());
66+
}
1967
"#,
2068
)
69+
.test();
70+
}
71+
72+
#[test]
73+
fn valid_enum_return() {
74+
project()
75+
.file("shape.webidl", SHAPE_IDL)
2176
.file(
2277
"shape.mjs",
2378
r#"
@@ -33,6 +88,10 @@ fn top_level_enum() {
3388
isCircle() {
3489
return this.kind === 'circle';
3590
}
91+
92+
getShape() {
93+
return this.kind;
94+
}
3695
}
3796
"#,
3897
)
@@ -55,8 +114,64 @@ fn top_level_enum() {
55114
let square = Shape::new(ShapeType::Square).unwrap();
56115
assert!(circle.is_circle());
57116
assert!(!circle.is_square());
117+
assert_eq!(circle.get_shape(), ShapeType::Circle);
58118
assert!(square.is_square());
59119
assert!(!square.is_circle());
120+
assert_eq!(square.get_shape(), ShapeType::Square);
121+
}
122+
"#,
123+
)
124+
.test();
125+
}
126+
127+
#[test]
128+
fn invalid_enum_return() {
129+
project()
130+
.file("shape.webidl", SHAPE_IDL)
131+
.file(
132+
"shape.mjs",
133+
r#"
134+
export class Shape {
135+
constructor(kind) {
136+
this.kind = 'triangle'; // <-- invalid ShapeType
137+
}
138+
139+
isSquare() {
140+
return this.kind === 'square';
141+
}
142+
143+
isCircle() {
144+
return this.kind === 'circle';
145+
}
146+
147+
getShape() {
148+
return this.kind;
149+
}
150+
}
151+
"#,
152+
)
153+
.file(
154+
"src/lib.rs",
155+
r#"
156+
#![feature(use_extern_macros)]
157+
158+
extern crate wasm_bindgen;
159+
160+
use wasm_bindgen::prelude::*;
161+
162+
pub mod shape;
163+
164+
use shape::{Shape, ShapeType};
165+
166+
#[wasm_bindgen]
167+
pub fn test() {
168+
let actually_a_triangle = Shape::new(ShapeType::Circle).unwrap();
169+
assert!(!actually_a_triangle.is_circle());
170+
assert!(!actually_a_triangle.is_square());
171+
match actually_a_triangle.get_shape() {
172+
ShapeType::Circle | ShapeType::Square => assert!(false),
173+
_ => {} // Success
174+
};
60175
}
61176
"#,
62177
)

0 commit comments

Comments
 (0)