Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved support for string enums #4147

Merged
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions crates/backend/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Ident>,
/// The JS string values of the variants
pub variant_values: Vec<String>,
/// The doc comments on this enum, if any
pub comments: Vec<String>,
/// Attributes to apply to the Rust enum
pub rust_attrs: Vec<syn::Attribute>,
/// Whether to generate a typescript definition for this enum
pub generate_typescript: bool,
/// Path to wasm_bindgen
pub wasm_bindgen: Path,
}
Expand Down
12 changes: 1 addition & 11 deletions crates/backend/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -1257,7 +1248,6 @@ impl ToTokens for ast::StringEnum {
inform(#name_len);
#(inform(#name_chars);)*
inform(#variant_count);
#(#describe_variants)*
}
}

Expand Down
10 changes: 8 additions & 2 deletions crates/backend/src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
3 changes: 0 additions & 3 deletions crates/cli-support/src/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ pub enum Descriptor {
name: String,
invalid: u32,
hole: u32,
variant_values: Vec<String>,
},
RustStruct(String),
Char,
Expand Down Expand Up @@ -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 => {
Expand Down
46 changes: 14 additions & 32 deletions crates/cli-support/src/js/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -576,20 +576,11 @@ fn instruction(
log_error: &mut bool,
constructor: &Option<String>,
) -> 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
Expand All @@ -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 {
Expand Down Expand Up @@ -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))
// |
Expand Down
43 changes: 41 additions & 2 deletions crates/cli-support/src/js/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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)?;
Expand Down Expand Up @@ -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);
Expand Down
7 changes: 3 additions & 4 deletions crates/cli-support/src/wit/incoming.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down Expand Up @@ -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,
},
Expand Down
29 changes: 28 additions & 1 deletion crates/cli-support/src/wit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
}

Expand Down Expand Up @@ -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(),
Expand Down
17 changes: 17 additions & 0 deletions crates/cli-support/src/wit/nonstandard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ pub struct WasmBindgenAux {
/// Auxiliary information to go into JS/TypeScript bindings describing the
/// exported enums from Rust.
pub enums: HashMap<String, AuxEnum>,
/// Auxiliary information to go into JS/TypeScript bindings describing the
/// exported string enums from Rust.
pub string_enums: HashMap<String, AuxStringEnum>,

/// Auxiliary information to go into JS/TypeScript bindings describing the
/// exported structs from Rust and their fields they've got exported.
Expand Down Expand Up @@ -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<String>,
/// Whether typescript bindings should be generated for this enum.
pub generate_typescript: bool,
}

#[derive(Debug)]
pub struct AuxStruct {
/// The name of this struct
Expand Down
Loading