From 0be2fe5f54739db09981055359726a085b0d5ebe Mon Sep 17 00:00:00 2001 From: Michael Schmidt Date: Tue, 8 Oct 2024 23:16:09 +0200 Subject: [PATCH] Improved support for string enums (#4147) --- CHANGELOG.md | 6 ++ crates/backend/src/ast.rs | 6 ++ crates/backend/src/codegen.rs | 12 +--- crates/backend/src/encode.rs | 10 +++- crates/cli-support/src/descriptor.rs | 3 - crates/cli-support/src/js/binding.rs | 46 +++++---------- crates/cli-support/src/js/mod.rs | 43 +++++++++++++- crates/cli-support/src/wit/incoming.rs | 7 +-- crates/cli-support/src/wit/mod.rs | 29 ++++++++- crates/cli-support/src/wit/nonstandard.rs | 17 ++++++ crates/cli-support/src/wit/outgoing.rs | 20 ++----- crates/cli-support/src/wit/standard.rs | 8 +-- crates/cli/tests/reference/enums.d.ts | 12 ++++ crates/cli/tests/reference/enums.js | 15 ++++- crates/cli/tests/reference/enums.rs | 17 ++++++ crates/macro-support/src/parser.rs | 68 +++++++++++++--------- crates/macro/ui-tests/invalid-enums.rs | 19 ++++++ crates/macro/ui-tests/invalid-enums.stderr | 32 +++++++--- crates/shared/src/lib.rs | 10 +++- crates/shared/src/schema_hash_approval.rs | 2 +- 20 files changed, 268 insertions(+), 114 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1714b743e7..9ca69427762 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,9 @@ * Added `Debug` implementation to `JsError`. [#4136](https://github.com/rustwasm/wasm-bindgen/pull/4136) +* Added support for `js_name` and `skip_typescript` attributes for string enums. + [#4147](https://github.com/rustwasm/wasm-bindgen/pull/4147) + ### Changed * Implicitly enable reference type and multivalue transformations if the module already makes use of the corresponding target features. @@ -79,6 +82,9 @@ * Specify `"type": "module"` when deploying to nodejs-module [#4092](https://github.com/rustwasm/wasm-bindgen/pull/4092) +* Fixed string enums not generating TypeScript types. + [#4147](https://github.com/rustwasm/wasm-bindgen/pull/4147) + -------------------------------------------------------------------------------- ## [0.2.93](https://github.com/rustwasm/wasm-bindgen/compare/0.2.92...0.2.93) diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index d27e1e746b8..d745f325f9e 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -337,12 +337,18 @@ pub struct StringEnum { pub vis: syn::Visibility, /// The Rust enum's identifiers pub name: Ident, + /// The name of this string enum in JS/TS code + pub js_name: String, /// The Rust identifiers for the variants pub variants: Vec, /// The JS string values of the variants pub variant_values: Vec, + /// The doc comments on this enum, if any + pub comments: Vec, /// Attributes to apply to the Rust enum pub rust_attrs: Vec, + /// Whether to generate a typescript definition for this enum + pub generate_typescript: bool, /// Path to wasm_bindgen pub wasm_bindgen: Path, } diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 89c543e257b..b6371d65974 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -1144,7 +1144,7 @@ impl ToTokens for ast::StringEnum { fn to_tokens(&self, tokens: &mut TokenStream) { let vis = &self.vis; let enum_name = &self.name; - let name_str = enum_name.to_string(); + let name_str = self.js_name.to_string(); let name_len = name_str.len() as u32; let name_chars = name_str.chars().map(u32::from); let variants = &self.variants; @@ -1172,15 +1172,6 @@ impl ToTokens for ast::StringEnum { let wasm_bindgen = &self.wasm_bindgen; - let describe_variants = self.variant_values.iter().map(|variant_value| { - let length = variant_value.len() as u32; - let chars = variant_value.chars().map(u32::from); - quote! { - inform(#length); - #(inform(#chars);)* - } - }); - (quote! { #(#attrs)* #[non_exhaustive] @@ -1257,7 +1248,6 @@ impl ToTokens for ast::StringEnum { inform(#name_len); #(inform(#name_chars);)* inform(#variant_count); - #(#describe_variants)* } } diff --git a/crates/backend/src/encode.rs b/crates/backend/src/encode.rs index 44f36942b36..8e89e58a81e 100644 --- a/crates/backend/src/encode.rs +++ b/crates/backend/src/encode.rs @@ -359,8 +359,14 @@ fn shared_import_type<'a>(i: &'a ast::ImportType, intern: &'a Interner) -> Impor } } -fn shared_import_enum<'a>(_i: &'a ast::StringEnum, _intern: &'a Interner) -> StringEnum { - StringEnum {} +fn shared_import_enum<'a>(i: &'a ast::StringEnum, _intern: &'a Interner) -> StringEnum<'a> { + StringEnum { + name: &i.js_name, + public: matches!(i.vis, syn::Visibility::Public(_)), + generate_typescript: i.generate_typescript, + variant_values: i.variant_values.iter().map(|x| &**x).collect(), + comments: i.comments.iter().map(|s| &**s).collect(), + } } fn shared_struct<'a>(s: &'a ast::Struct, intern: &'a Interner) -> Struct<'a> { diff --git a/crates/cli-support/src/descriptor.rs b/crates/cli-support/src/descriptor.rs index 6a35786eba5..5dec8d86d71 100644 --- a/crates/cli-support/src/descriptor.rs +++ b/crates/cli-support/src/descriptor.rs @@ -76,7 +76,6 @@ pub enum Descriptor { name: String, invalid: u32, hole: u32, - variant_values: Vec, }, RustStruct(String), Char, @@ -171,12 +170,10 @@ impl Descriptor { let variant_count = get(data); let invalid = variant_count; let hole = variant_count + 1; - let variant_values = (0..variant_count).map(|_| get_string(data)).collect(); Descriptor::StringEnum { name, invalid, hole, - variant_values, } } RUST_STRUCT => { diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index cef04592571..bb4864e54a8 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -576,20 +576,11 @@ fn instruction( log_error: &mut bool, constructor: &Option, ) -> Result<(), Error> { - fn wasm_to_string_enum(variant_values: &[String], index: &str) -> String { + fn wasm_to_string_enum(name: &str, index: &str) -> String { // e.g. ["a","b","c"][someIndex] - let mut enum_val_expr = String::new(); - enum_val_expr.push('['); - for variant in variant_values { - enum_val_expr.push_str(&format!("\"{variant}\",")); - } - enum_val_expr.push(']'); - enum_val_expr.push('['); - enum_val_expr.push_str(index); - enum_val_expr.push(']'); - enum_val_expr + format!("__wbindgen_enum_{name}[{index}]") } - fn string_enum_to_wasm(variant_values: &[String], invalid: u32, enum_val: &str) -> String { + fn string_enum_to_wasm(name: &str, invalid: u32, enum_val: &str) -> String { // e.g. (["a","b","c"].indexOf(someEnumVal) + 1 || 4) - 1 // | // invalid + 1 @@ -598,16 +589,10 @@ fn instruction( // and with +1 we get 0 which is falsey, so we can use || to // substitute invalid+1. Finally, we just do -1 to get the correct // values for everything. - let mut enum_val_expr = String::new(); - enum_val_expr.push_str("(["); - for variant in variant_values.iter() { - enum_val_expr.push_str(&format!("\"{variant}\",")); - } - enum_val_expr.push_str(&format!( - "].indexOf({enum_val}) + 1 || {invalid}) - 1", + format!( + "(__wbindgen_enum_{name}.indexOf({enum_val}) + 1 || {invalid}) - 1", invalid = invalid + 1 - )); - enum_val_expr + ) } match instr { @@ -702,34 +687,31 @@ fn instruction( } } - Instruction::WasmToStringEnum { variant_values } => { + Instruction::WasmToStringEnum { name } => { let index = js.pop(); - js.push(wasm_to_string_enum(variant_values, &index)) + js.push(wasm_to_string_enum(name, &index)) } - Instruction::OptionWasmToStringEnum { variant_values, .. } => { + Instruction::OptionWasmToStringEnum { name, .. } => { // Since hole is currently variant_count+1 and the lookup is // ["a","b","c"][index], the lookup will implicitly return map // the hole to undefined, because OOB indexes will return undefined. let index = js.pop(); - js.push(wasm_to_string_enum(variant_values, &index)) + js.push(wasm_to_string_enum(name, &index)) } - Instruction::StringEnumToWasm { - variant_values, - invalid, - } => { + Instruction::StringEnumToWasm { name, invalid } => { let enum_val = js.pop(); - js.push(string_enum_to_wasm(variant_values, *invalid, &enum_val)) + js.push(string_enum_to_wasm(name, *invalid, &enum_val)) } Instruction::OptionStringEnumToWasm { - variant_values, + name, invalid, hole, } => { let enum_val = js.pop(); - let enum_val_expr = string_enum_to_wasm(variant_values, *invalid, &enum_val); + let enum_val_expr = string_enum_to_wasm(name, *invalid, &enum_val); // e.g. someEnumVal == undefined ? 4 : (string_enum_to_wasm(someEnumVal)) // | diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 6256679021c..8a150de5816 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -2,7 +2,7 @@ use crate::descriptor::VectorKind; use crate::intrinsic::Intrinsic; use crate::wit::{ Adapter, AdapterId, AdapterJsImportKind, AdapterType, AuxExportedMethodKind, AuxReceiverKind, - AuxValue, + AuxStringEnum, AuxValue, }; use crate::wit::{AdapterKind, Instruction, InstructionData}; use crate::wit::{AuxEnum, AuxExport, AuxExportKind, AuxImport, AuxStruct}; @@ -2529,9 +2529,12 @@ __wbg_set_wasm(wasm);" pairs.sort_by_key(|(k, _)| *k); check_duplicated_getter_and_setter_names(&pairs)?; - for (_, e) in self.aux.enums.iter() { + for (_, e) in crate::sorted_iter(&self.aux.enums) { self.generate_enum(e)?; } + for (_, e) in crate::sorted_iter(&self.aux.string_enums) { + self.generate_string_enum(e)?; + } for s in self.aux.structs.iter() { self.generate_struct(s)?; @@ -3808,6 +3811,42 @@ __wbg_set_wasm(wasm);" Ok(()) } + fn generate_string_enum(&mut self, string_enum: &AuxStringEnum) -> Result<(), Error> { + let docs = format_doc_comments(&string_enum.comments, None); + + let variants: Vec<_> = string_enum + .variant_values + .iter() + .map(|v| format!("\"{v}\"")) + .collect(); + + if string_enum.generate_typescript { + self.typescript.push_str(&docs); + if string_enum.public { + self.typescript.push_str("export "); + } + self.typescript.push_str("type "); + self.typescript.push_str(&string_enum.name); + self.typescript.push_str(" = "); + + if variants.is_empty() { + self.typescript.push_str("never"); + } else { + self.typescript.push_str(&variants.join(" | ")); + } + + self.typescript.push_str(";\n"); + } + + self.global(&format!( + "const __wbindgen_enum_{name} = [{values}];\n", + name = string_enum.name, + values = variants.join(", ") + )); + + Ok(()) + } + fn generate_struct(&mut self, struct_: &AuxStruct) -> Result<(), Error> { let class = require_class(&mut self.exported_classes, &struct_.name); class.comments = format_doc_comments(&struct_.comments, None); diff --git a/crates/cli-support/src/wit/incoming.rs b/crates/cli-support/src/wit/incoming.rs index 73571763834..a1a6ee196b4 100644 --- a/crates/cli-support/src/wit/incoming.rs +++ b/crates/cli-support/src/wit/incoming.rs @@ -108,11 +108,11 @@ impl InstructionBuilder<'_, '_> { &[AdapterType::I32], ); }, - Descriptor::StringEnum { name, variant_values, invalid, .. } => { + Descriptor::StringEnum { name, invalid, .. } => { self.instruction( &[AdapterType::StringEnum(name.clone())], Instruction::StringEnumToWasm { - variant_values: variant_values.clone(), + name: name.clone(), invalid: *invalid, }, &[AdapterType::I32], @@ -308,14 +308,13 @@ impl InstructionBuilder<'_, '_> { } Descriptor::StringEnum { name, - variant_values, invalid, hole, } => { self.instruction( &[AdapterType::StringEnum(name.clone()).option()], Instruction::OptionStringEnumToWasm { - variant_values: variant_values.clone(), + name: name.clone(), invalid: *invalid, hole: *hole, }, diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index fb1825e01d7..ce573ff64ff 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -573,7 +573,7 @@ impl<'a> Context<'a> { decode::ImportKind::Static(s) => self.import_static(&import, s), decode::ImportKind::String(s) => self.import_string(s), decode::ImportKind::Type(t) => self.import_type(&import, t), - decode::ImportKind::Enum(_) => Ok(()), + decode::ImportKind::Enum(e) => self.string_enum(e), } } @@ -865,6 +865,33 @@ impl<'a> Context<'a> { Ok(()) } + fn string_enum(&mut self, string_enum: &decode::StringEnum<'_>) -> Result<(), Error> { + let aux = AuxStringEnum { + name: string_enum.name.to_string(), + public: string_enum.public, + comments: concatenate_comments(&string_enum.comments), + variant_values: string_enum + .variant_values + .iter() + .map(|v| v.to_string()) + .collect(), + generate_typescript: string_enum.generate_typescript, + }; + let mut result = Ok(()); + self.aux + .string_enums + .entry(aux.name.clone()) + .and_modify(|existing| { + result = Err(anyhow!( + "duplicate string enums:\n{:?}\n{:?}", + existing, + aux + )); + }) + .or_insert(aux); + result + } + fn enum_(&mut self, enum_: decode::Enum<'_>) -> Result<(), Error> { let aux = AuxEnum { name: enum_.name.to_string(), diff --git a/crates/cli-support/src/wit/nonstandard.rs b/crates/cli-support/src/wit/nonstandard.rs index 5099a72098c..b7777c906bf 100644 --- a/crates/cli-support/src/wit/nonstandard.rs +++ b/crates/cli-support/src/wit/nonstandard.rs @@ -42,6 +42,9 @@ pub struct WasmBindgenAux { /// Auxiliary information to go into JS/TypeScript bindings describing the /// exported enums from Rust. pub enums: HashMap, + /// Auxiliary information to go into JS/TypeScript bindings describing the + /// exported string enums from Rust. + pub string_enums: HashMap, /// Auxiliary information to go into JS/TypeScript bindings describing the /// exported structs from Rust and their fields they've got exported. @@ -172,6 +175,20 @@ pub struct AuxEnum { pub generate_typescript: bool, } +#[derive(Debug)] +pub struct AuxStringEnum { + /// The name of this enum + pub name: String, + /// Whether this enum is public + pub public: bool, + /// The copied Rust comments to forward to JS + pub comments: String, + /// A list of variants values + pub variant_values: Vec, + /// Whether typescript bindings should be generated for this enum. + pub generate_typescript: bool, +} + #[derive(Debug)] pub struct AuxStruct { /// The name of this struct diff --git a/crates/cli-support/src/wit/outgoing.rs b/crates/cli-support/src/wit/outgoing.rs index 6bc647cf9c6..020d8b1702f 100644 --- a/crates/cli-support/src/wit/outgoing.rs +++ b/crates/cli-support/src/wit/outgoing.rs @@ -74,12 +74,7 @@ impl InstructionBuilder<'_, '_> { self.output.push(AdapterType::F64); } Descriptor::Enum { name, .. } => self.outgoing_i32(AdapterType::Enum(name.clone())), - Descriptor::StringEnum { - name, - variant_values, - invalid: _, - hole: _, - } => self.outgoing_string_enum(name, variant_values), + Descriptor::StringEnum { name, .. } => self.outgoing_string_enum(name), Descriptor::Char => { self.instruction( @@ -293,16 +288,11 @@ impl InstructionBuilder<'_, '_> { &[AdapterType::Enum(name.clone()).option()], ); } - Descriptor::StringEnum { - name, - invalid: _, - hole, - variant_values, - } => { + Descriptor::StringEnum { name, hole, .. } => { self.instruction( &[AdapterType::I32], Instruction::OptionWasmToStringEnum { - variant_values: variant_values.to_vec(), + name: name.clone(), hole: *hole, }, &[AdapterType::StringEnum(String::from(name)).option()], @@ -542,11 +532,11 @@ impl InstructionBuilder<'_, '_> { self.instruction(&[AdapterType::I32], instr, &[output]); } - fn outgoing_string_enum(&mut self, name: &str, variant_values: &[String]) { + fn outgoing_string_enum(&mut self, name: &str) { self.instruction( &[AdapterType::I32], Instruction::WasmToStringEnum { - variant_values: variant_values.to_vec(), + name: name.to_string(), }, &[AdapterType::StringEnum(String::from(name))], ); diff --git a/crates/cli-support/src/wit/standard.rs b/crates/cli-support/src/wit/standard.rs index c93c4ef4d3f..c96430c6411 100644 --- a/crates/cli-support/src/wit/standard.rs +++ b/crates/cli-support/src/wit/standard.rs @@ -143,22 +143,22 @@ pub enum Instruction { /// Pops a Wasm `i32` and pushes the enum variant as a string WasmToStringEnum { - variant_values: Vec, + name: String, }, OptionWasmToStringEnum { - variant_values: Vec, + name: String, hole: u32, }, /// pops a string and pushes the enum variant as an `i32` StringEnumToWasm { - variant_values: Vec, + name: String, invalid: u32, }, OptionStringEnumToWasm { - variant_values: Vec, + name: String, invalid: u32, hole: u32, }, diff --git a/crates/cli/tests/reference/enums.d.ts b/crates/cli/tests/reference/enums.d.ts index 556f8085651..e6c6229b926 100644 --- a/crates/cli/tests/reference/enums.d.ts +++ b/crates/cli/tests/reference/enums.d.ts @@ -20,8 +20,20 @@ export function get_name(color: Color): ColorName; * @returns {ColorName | undefined} */ export function option_string_enum_echo(color?: ColorName): ColorName | undefined; +/** + * A color. + */ export enum Color { Green = 0, Yellow = 1, Red = 2, } +/** + * The name of a color. + */ +export type ColorName = "green" | "yellow" | "red"; +/** + * An unused string enum. + */ +export type FooBar = "foo" | "bar"; +type PrivateStringEnum = "foo" | "bar"; diff --git a/crates/cli/tests/reference/enums.js b/crates/cli/tests/reference/enums.js index bbfdc2c5859..a2ef36d4aaf 100644 --- a/crates/cli/tests/reference/enums.js +++ b/crates/cli/tests/reference/enums.js @@ -50,7 +50,7 @@ export function option_enum_echo(color) { */ export function get_name(color) { const ret = wasm.get_name(color); - return ["green","yellow","red",][ret]; + return __wbindgen_enum_ColorName[ret]; } /** @@ -58,12 +58,21 @@ export function get_name(color) { * @returns {ColorName | undefined} */ export function option_string_enum_echo(color) { - const ret = wasm.option_string_enum_echo(color == undefined ? 4 : ((["green","yellow","red",].indexOf(color) + 1 || 4) - 1)); - return ["green","yellow","red",][ret]; + const ret = wasm.option_string_enum_echo(color == undefined ? 4 : ((__wbindgen_enum_ColorName.indexOf(color) + 1 || 4) - 1)); + return __wbindgen_enum_ColorName[ret]; } +/** + * A color. + */ export const Color = Object.freeze({ Green:0,"0":"Green",Yellow:1,"1":"Yellow",Red:2,"2":"Red", }); +const __wbindgen_enum_ColorName = ["green", "yellow", "red"]; + +const __wbindgen_enum_FooBar = ["foo", "bar"]; + +const __wbindgen_enum_PrivateStringEnum = ["foo", "bar"]; + export function __wbindgen_throw(arg0, arg1) { throw new Error(getStringFromWasm0(arg0, arg1)); }; diff --git a/crates/cli/tests/reference/enums.rs b/crates/cli/tests/reference/enums.rs index e4cc8e9794c..7e588df0f4d 100644 --- a/crates/cli/tests/reference/enums.rs +++ b/crates/cli/tests/reference/enums.rs @@ -1,5 +1,6 @@ use wasm_bindgen::prelude::*; +/// A color. #[wasm_bindgen] #[derive(PartialEq, Debug)] pub enum Color { @@ -18,6 +19,7 @@ pub fn option_enum_echo(color: Option) -> Option { color } +/// The name of a color. #[wasm_bindgen] #[derive(PartialEq, Debug)] pub enum ColorName { @@ -39,3 +41,18 @@ pub fn get_name(color: Color) -> ColorName { pub fn option_string_enum_echo(color: Option) -> Option { color } + +/// An unused string enum. +#[wasm_bindgen(js_name = "FooBar")] +#[derive(PartialEq, Debug)] +pub enum UnusedStringEnum { + Foo = "foo", + Bar = "bar", +} + +#[wasm_bindgen] +#[derive(PartialEq, Debug)] +enum PrivateStringEnum { + Foo = "foo", + Bar = "bar", +} diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 338c931c598..b1bf4e1295f 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -1303,20 +1303,21 @@ impl<'a> MacroParse<&ClassMarker> for &'a mut syn::ImplItemFn { } } -fn string_enum(enum_: syn::ItemEnum, program: &mut ast::Program) -> Result<(), Diagnostic> { +fn string_enum( + enum_: syn::ItemEnum, + program: &mut ast::Program, + js_name: String, + generate_typescript: bool, + comments: Vec, +) -> Result<(), Diagnostic> { let mut variants = vec![]; let mut variant_values = vec![]; for v in enum_.variants.iter() { - match v.fields { - syn::Fields::Unit => (), - _ => bail_span!(v.fields, "only C-Style enums allowed with #[wasm_bindgen]"), - } - let (_, expr) = match &v.discriminant { Some(pair) => pair, None => { - bail_span!(v, "all variants must have a value"); + bail_span!(v, "all variants of a string enum must have a string value"); } }; match get_expr(expr) { @@ -1340,9 +1341,12 @@ fn string_enum(enum_: syn::ItemEnum, program: &mut ast::Program) -> Result<(), D kind: ast::ImportKind::Enum(ast::StringEnum { vis: enum_.vis, name: enum_.ident, + js_name, variants, variant_values, + comments, rust_attrs: enum_.attrs, + generate_typescript, wasm_bindgen: program.wasm_bindgen.clone(), }), }); @@ -1359,25 +1363,42 @@ impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum { if self.variants.is_empty() { bail_span!(self, "cannot export empty enums to JS"); } - let generate_typescript = opts.skip_typescript().is_none(); - - // Check if the first value is a string literal - if let Some((_, expr)) = &self.variants[0].discriminant { - if let syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Str(_), - .. - }) = get_expr(expr) - { - opts.check_used(); - return string_enum(self, program); + for variant in self.variants.iter() { + match variant.fields { + syn::Fields::Unit => (), + _ => bail_span!( + variant.fields, + "enum variants with associated data are not supported with #[wasm_bindgen]" + ), } } + + let generate_typescript = opts.skip_typescript().is_none(); let js_name = opts .js_name() .map(|s| s.0) .map_or_else(|| self.ident.to_string(), |s| s.to_string()); + let comments = extract_doc_comments(&self.attrs); + opts.check_used(); + // Check if the enum is a string enum, by checking whether any variant has a string discriminant. + let is_string_enum = self.variants.iter().any(|v| { + if let Some((_, expr)) = &v.discriminant { + if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(_), + .. + }) = get_expr(expr) + { + return true; + } + } + false + }); + if is_string_enum { + return string_enum(self, program, js_name, generate_typescript, comments); + } + let has_discriminant = self.variants[0].discriminant.is_some(); match self.vis { @@ -1390,11 +1411,6 @@ impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum { .iter() .enumerate() .map(|(i, v)| { - match v.fields { - syn::Fields::Unit => (), - _ => 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. @@ -1415,14 +1431,14 @@ impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum { Err(_) => { bail_span!( int_lit, - "enums with #[wasm_bindgen] can only support \ + "C-style enums with #[wasm_bindgen] can only support \ numbers that can be represented as u32" ); } }, expr => bail_span!( expr, - "enums with #[wasm_bindgen] may only have \ + "C-style enums with #[wasm_bindgen] may only have \ number literal values", ), }, @@ -1454,8 +1470,6 @@ impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum { assert!(hole != value); } - let comments = extract_doc_comments(&self.attrs); - self.to_tokens(tokens); program.enums.push(ast::Enum { diff --git a/crates/macro/ui-tests/invalid-enums.rs b/crates/macro/ui-tests/invalid-enums.rs index abea3fa6712..3ddd5e17487 100644 --- a/crates/macro/ui-tests/invalid-enums.rs +++ b/crates/macro/ui-tests/invalid-enums.rs @@ -18,4 +18,23 @@ pub enum D { X = 4294967296, } +#[wasm_bindgen] +pub enum E { + A = 1, + B = "foo", +} + +#[wasm_bindgen] +pub enum F { + A = "foo", + B = 1, +} + +#[wasm_bindgen] +enum G { + A = "foo", + B = "bar", + C, +} + fn main() {} diff --git a/crates/macro/ui-tests/invalid-enums.stderr b/crates/macro/ui-tests/invalid-enums.stderr index 7ad51f9a5a2..13199f8f868 100644 --- a/crates/macro/ui-tests/invalid-enums.stderr +++ b/crates/macro/ui-tests/invalid-enums.stderr @@ -1,23 +1,41 @@ error: cannot export empty enums to JS - --> $DIR/invalid-enums.rs:4:1 + --> ui-tests/invalid-enums.rs:4:1 | 4 | enum A {} | ^^^^^^^^^ -error: only C-Style enums allowed with #[wasm_bindgen] - --> $DIR/invalid-enums.rs:8:6 +error: enum variants with associated data are not supported with #[wasm_bindgen] + --> ui-tests/invalid-enums.rs:8:6 | 8 | D(u32), | ^^^^^ -error: enums with #[wasm_bindgen] may only have number literal values - --> $DIR/invalid-enums.rs:13:9 +error: C-style enums with #[wasm_bindgen] may only have number literal values + --> ui-tests/invalid-enums.rs:13:9 | 13 | X = 1 + 3, | ^^^^^ -error: enums with #[wasm_bindgen] can only support numbers that can be represented as u32 - --> $DIR/invalid-enums.rs:18:9 +error: C-style enums with #[wasm_bindgen] can only support numbers that can be represented as u32 + --> ui-tests/invalid-enums.rs:18:9 | 18 | X = 4294967296, | ^^^^^^^^^^ + +error: enums with #[wasm_bindgen] cannot mix string and non-string values + --> ui-tests/invalid-enums.rs:23:9 + | +23 | A = 1, + | ^ + +error: enums with #[wasm_bindgen] cannot mix string and non-string values + --> ui-tests/invalid-enums.rs:30:9 + | +30 | B = 1, + | ^ + +error: all variants of a string enum must have a string value + --> ui-tests/invalid-enums.rs:37:5 + | +37 | C, + | ^ diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 0b9af3b581b..aa3964ec0a4 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -51,7 +51,7 @@ macro_rules! shared_api { Static(ImportStatic<'a>), String(ImportString<'a>), Type(ImportType<'a>), - Enum(StringEnum), + Enum(StringEnum<'a>), } struct ImportFunction<'a> { @@ -104,7 +104,13 @@ macro_rules! shared_api { vendor_prefixes: Vec<&'a str>, } - struct StringEnum {} + struct StringEnum<'a> { + name: &'a str, + public: bool, + variant_values: Vec<&'a str>, + comments: Vec<&'a str>, + generate_typescript: bool, + } struct Export<'a> { class: Option<&'a str>, diff --git a/crates/shared/src/schema_hash_approval.rs b/crates/shared/src/schema_hash_approval.rs index 1de5ee31cd3..ad4af57a09e 100644 --- a/crates/shared/src/schema_hash_approval.rs +++ b/crates/shared/src/schema_hash_approval.rs @@ -8,7 +8,7 @@ // If the schema in this library has changed then: // 1. Bump the version in `crates/shared/Cargo.toml` // 2. Change the `SCHEMA_VERSION` in this library to this new Cargo.toml version -const APPROVED_SCHEMA_FILE_HASH: &str = "9336383503182818021"; +const APPROVED_SCHEMA_FILE_HASH: &str = "9179028021460341559"; #[test] fn schema_version() {