Merge pull request #1214 from alexcrichton/enum-option

Support `Option` with custom enums in JS
This commit is contained in:
Alex Crichton 2019-01-29 09:57:51 -06:00 committed by GitHub
commit cf1e1e0dc1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 119 additions and 5 deletions

View File

@ -207,6 +207,7 @@ pub struct Enum {
pub name: Ident, pub name: Ident,
pub variants: Vec<Variant>, pub variants: Vec<Variant>,
pub comments: Vec<String>, pub comments: Vec<String>,
pub hole: u32,
} }
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]

View File

@ -1013,6 +1013,7 @@ impl<'a> ToTokens for DescribeImport<'a> {
impl ToTokens for ast::Enum { impl ToTokens for ast::Enum {
fn to_tokens(&self, into: &mut TokenStream) { fn to_tokens(&self, into: &mut TokenStream) {
let enum_name = &self.name; let enum_name = &self.name;
let hole = &self.hole;
let cast_clauses = self.variants.iter().map(|variant| { let cast_clauses = self.variants.iter().map(|variant| {
let variant_name = &variant.name; let variant_name = &variant.name;
quote! { quote! {
@ -1034,6 +1035,7 @@ impl ToTokens for ast::Enum {
impl ::wasm_bindgen::convert::FromWasmAbi for #enum_name { impl ::wasm_bindgen::convert::FromWasmAbi for #enum_name {
type Abi = u32; type Abi = u32;
#[inline]
unsafe fn from_abi( unsafe fn from_abi(
js: u32, js: u32,
_extra: &mut ::wasm_bindgen::convert::Stack, _extra: &mut ::wasm_bindgen::convert::Stack,
@ -1044,10 +1046,21 @@ impl ToTokens for ast::Enum {
} }
} }
impl ::wasm_bindgen::convert::OptionFromWasmAbi for #enum_name {
#[inline]
fn is_none(val: &u32) -> bool { *val == #hole }
}
impl ::wasm_bindgen::convert::OptionIntoWasmAbi for #enum_name {
#[inline]
fn none() -> Self::Abi { #hole }
}
impl ::wasm_bindgen::describe::WasmDescribe for #enum_name { impl ::wasm_bindgen::describe::WasmDescribe for #enum_name {
fn describe() { fn describe() {
use wasm_bindgen::describe::*; use wasm_bindgen::describe::*;
inform(ENUM); inform(ENUM);
inform(#hole);
} }
} }
}) })

View File

@ -59,7 +59,7 @@ pub enum Descriptor {
Vector(Box<Descriptor>), Vector(Box<Descriptor>),
String, String,
Anyref, Anyref,
Enum, Enum { hole: u32 },
RustStruct(String), RustStruct(String),
Char, Char,
Option(Box<Descriptor>), Option(Box<Descriptor>),
@ -128,7 +128,7 @@ impl Descriptor {
OPTIONAL => Descriptor::Option(Box::new(Descriptor::_decode(data))), OPTIONAL => Descriptor::Option(Box::new(Descriptor::_decode(data))),
STRING => Descriptor::String, STRING => Descriptor::String,
ANYREF => Descriptor::Anyref, ANYREF => Descriptor::Anyref,
ENUM => Descriptor::Enum, ENUM => Descriptor::Enum { hole: get(data) },
RUST_STRUCT => { RUST_STRUCT => {
let name = (0..get(data)) let name = (0..get(data))
.map(|_| char::from_u32(get(data)).unwrap()) .map(|_| char::from_u32(get(data)).unwrap())
@ -159,7 +159,7 @@ impl Descriptor {
| Descriptor::U32 | Descriptor::U32
| Descriptor::F32 | Descriptor::F32
| Descriptor::F64 | Descriptor::F64
| Descriptor::Enum => true, | Descriptor::Enum { .. } => true,
_ => return false, _ => return false,
} }
} }

View File

@ -306,6 +306,14 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
.push(format!("isLikeNone({0}) ? 0 : {0}.codePointAt(0)", name)); .push(format!("isLikeNone({0}) ? 0 : {0}.codePointAt(0)", name));
return Ok(self); return Ok(self);
} }
Descriptor::Enum { hole } => {
self.cx.expose_is_like_none();
self.js_arguments
.push((name.clone(), "number | undefined".to_string()));
self.rust_arguments
.push(format!("isLikeNone({0}) ? {1} : {0}", name, hole));
return Ok(self);
}
_ => bail!( _ => bail!(
"unsupported optional argument type for calling Rust function from JS: {:?}", "unsupported optional argument type for calling Rust function from JS: {:?}",
arg arg
@ -609,6 +617,14 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
.to_string(); .to_string();
return Ok(self); return Ok(self);
} }
Descriptor::Enum { hole } => {
self.ret_ty = "number | undefined".to_string();
self.ret_expr = format!("
const ret = RET;
return ret === {} ? undefined : ret;
", hole);
return Ok(self);
}
_ => bail!( _ => bail!(
"unsupported optional return type for calling Rust function from JS: {:?}", "unsupported optional return type for calling Rust function from JS: {:?}",
ty ty

View File

@ -200,6 +200,11 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
.push(format!("{0} === 0xFFFFFF ? undefined : {0} !== 0", abi)); .push(format!("{0} === 0xFFFFFF ? undefined : {0} !== 0", abi));
return Ok(()); return Ok(());
} }
Descriptor::Enum { hole } => {
self.js_arguments
.push(format!("{0} === {1} ? undefined : {0}", abi, hole));
return Ok(());
}
Descriptor::Char => { Descriptor::Char => {
let value = self.shim_argument(); let value = self.shim_argument();
self.js_arguments.push(format!( self.js_arguments.push(format!(
@ -441,6 +446,14 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
.to_string(); .to_string();
return Ok(()); return Ok(());
} }
Descriptor::Enum { hole } => {
self.cx.expose_is_like_none();
self.ret_expr = format!("
const val = JS;
return isLikeNone(val) ? {} : val;
", hole);
return Ok(());
}
_ => bail!( _ => bail!(
"unsupported optional return type for calling JS function from Rust: {:?}", "unsupported optional return type for calling JS function from Rust: {:?}",
ty ty

View File

@ -953,6 +953,12 @@ impl MacroParse<()> for syn::ItemEnum {
_ => bail_span!(self, "only public enums are allowed with #[wasm_bindgen]"), _ => bail_span!(self, "only public enums are allowed with #[wasm_bindgen]"),
} }
if self.variants.len() == 0 {
bail_span!(self, "cannot export empty enums to JS");
}
let has_discriminant = self.variants[0].discriminant.is_some();
let variants = self let variants = self
.variants .variants
.iter() .iter()
@ -962,6 +968,14 @@ impl MacroParse<()> for syn::ItemEnum {
syn::Fields::Unit => (), syn::Fields::Unit => (),
_ => bail_span!(v.fields, "only C-Style enums allowed with #[wasm_bindgen]"), _ => bail_span!(v.fields, "only C-Style enums allowed with #[wasm_bindgen]"),
} }
// Require that everything either has a discriminant or doesn't.
// We don't really want to get in the business of emulating how
// rustc assigns values to enums.
if v.discriminant.is_some() != has_discriminant {
bail_span!(v, "must either annotate discriminant of all variants or none");
}
let value = match v.discriminant { let value = match v.discriminant {
Some(( Some((
_, _,
@ -992,12 +1006,30 @@ impl MacroParse<()> for syn::ItemEnum {
value, value,
}) })
}) })
.collect::<Result<_, Diagnostic>>()?; .collect::<Result<Vec<_>, Diagnostic>>()?;
let mut values = variants.iter().map(|v| v.value).collect::<Vec<_>>();
values.sort();
let hole = values.windows(2)
.filter_map(|window| {
if window[0] + 1 != window[1] {
Some(window[0] + 1)
} else {
None
}
})
.next()
.unwrap_or(*values.last().unwrap() + 1);
for value in values {
assert!(hole != value);
}
let comments = extract_doc_comments(&self.attrs); let comments = extract_doc_comments(&self.attrs);
program.enums.push(ast::Enum { program.enums.push(ast::Enum {
name: self.ident, name: self.ident,
variants, variants,
comments, comments,
hole,
}); });
Ok(()) Ok(())
} }

View File

@ -18,3 +18,13 @@ exports.js_c_style_enum_with_custom_values = () => {
assert.strictEqual(wasm.enum_with_custom_values_cycle(wasm.ColorWithCustomValues.Green), wasm.ColorWithCustomValues.Yellow); assert.strictEqual(wasm.enum_with_custom_values_cycle(wasm.ColorWithCustomValues.Green), wasm.ColorWithCustomValues.Yellow);
}; };
exports.js_handle_optional_enums = x => wasm.handle_optional_enums(x);
exports.js_expect_enum = (a, b) => {
assert.strictEqual(a, b);
};
exports.js_expect_enum_none = a => {
assert.strictEqual(a, undefined);
};

View File

@ -6,9 +6,13 @@ use wasm_bindgen_test::*;
extern "C" { extern "C" {
fn js_c_style_enum(); fn js_c_style_enum();
fn js_c_style_enum_with_custom_values(); fn js_c_style_enum_with_custom_values();
fn js_handle_optional_enums(x: Option<Color>) -> Option<Color>;
fn js_expect_enum(x: Color, y: Option<Color>);
fn js_expect_enum_none(x: Option<Color>);
} }
#[wasm_bindgen] #[wasm_bindgen]
#[derive(PartialEq, Debug)]
pub enum Color { pub enum Color {
Green, Green,
Yellow, Yellow,
@ -22,7 +26,7 @@ pub mod inner {
pub enum ColorWithCustomValues { pub enum ColorWithCustomValues {
Green = 21, Green = 21,
Yellow = 34, Yellow = 34,
Red, Red = 2,
} }
} }
@ -53,3 +57,28 @@ fn c_style_enum() {
fn c_style_enum_with_custom_values() { fn c_style_enum_with_custom_values() {
js_c_style_enum_with_custom_values(); js_c_style_enum_with_custom_values();
} }
#[wasm_bindgen]
pub fn handle_optional_enums(x: Option<Color>) -> Option<Color> {
x
}
#[wasm_bindgen_test]
fn test_optional_enums() {
use self::Color::*;
assert_eq!(js_handle_optional_enums(None), None);
assert_eq!(js_handle_optional_enums(Some(Green)), Some(Green));
assert_eq!(js_handle_optional_enums(Some(Yellow)), Some(Yellow));
assert_eq!(js_handle_optional_enums(Some(Red)), Some(Red));
}
#[wasm_bindgen_test]
fn test_optional_enum_values() {
use self::Color::*;
js_expect_enum(Green, Some(Green));
js_expect_enum(Yellow, Some(Yellow));
js_expect_enum(Red, Some(Red));
js_expect_enum_none(None);
}