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

Add export_type attribute to export string enums #4260

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/backend/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,9 @@ pub struct StringEnum {
pub rust_attrs: Vec<syn::Attribute>,
/// Whether to generate a typescript definition for this enum
pub generate_typescript: bool,
/// Whether the type generated for this string enum should be publicly
/// exported as part of the API of the generated JS module
pub export_type: bool,
/// Path to wasm_bindgen
pub wasm_bindgen: Path,
}
Expand Down
1 change: 1 addition & 0 deletions crates/backend/src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ fn shared_import_enum<'a>(i: &'a ast::StringEnum, _intern: &'a Interner) -> Stri
StringEnum {
name: &i.js_name,
generate_typescript: i.generate_typescript,
export_type: i.export_type,
variant_values: i.variant_values.iter().map(|x| &**x).collect(),
comments: i.comments.iter().map(|s| &**s).collect(),
}
Expand Down
10 changes: 7 additions & 3 deletions crates/cli-support/src/js/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3972,9 +3972,10 @@ __wbg_set_wasm(wasm);"
.collect();

if string_enum.generate_typescript
&& self
.typescript_refs
.contains(&TsReference::StringEnum(string_enum.name.clone()))
&& (string_enum.export_type
|| self
.typescript_refs
.contains(&TsReference::StringEnum(string_enum.name.clone())))
{
let docs = format_doc_comments(&string_enum.comments, None);
let type_expr = if variants.is_empty() {
Expand All @@ -3984,6 +3985,9 @@ __wbg_set_wasm(wasm);"
};

self.typescript.push_str(&docs);
if string_enum.export_type {
self.typescript.push_str("export ");
}
self.typescript.push_str("type ");
self.typescript.push_str(&string_enum.name);
self.typescript.push_str(" = ");
Expand Down
1 change: 1 addition & 0 deletions crates/cli-support/src/wit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,7 @@ impl<'a> Context<'a> {
.map(|v| v.to_string())
.collect(),
generate_typescript: string_enum.generate_typescript,
export_type: string_enum.export_type,
};
let mut result = Ok(());
self.aux
Expand Down
2 changes: 2 additions & 0 deletions crates/cli-support/src/wit/nonstandard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ pub struct AuxStringEnum {
pub variant_values: Vec<String>,
/// Whether typescript bindings should be generated for this enum.
pub generate_typescript: bool,
/// Whether typescript bindings should be exported.
pub export_type: bool,
}

#[derive(Debug)]
Expand Down
4 changes: 4 additions & 0 deletions crates/cli/tests/reference/enums.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,7 @@ export enum Ordering {
* The name of a color.
*/
type ColorName = "green" | "yellow" | "red";
/**
* An unused string enum that has its typed exported.
*/
export type FooBarBaz = "foo" | "bar";
7 changes: 7 additions & 0 deletions crates/cli/tests/reference/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ pub enum UnusedStringEnum {
Bar = "bar",
}

/// An unused string enum that has its typed exported.
#[wasm_bindgen(js_name = "FooBarBaz", export_type)]
pub enum ExportedUnusedStringEnum {
Foo = "foo",
Bar = "bar",
}

#[wasm_bindgen]
enum PrivateStringEnum {
Foo = "foo",
Expand Down
20 changes: 17 additions & 3 deletions crates/macro-support/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ macro_rules! attrgen {
(typescript_type, TypeScriptType(Span, String, Span)),
(getter_with_clone, GetterWithClone(Span)),
(static_string, StaticString(Span)),
(export_type, ExportType(Span)),
(thread_local, ThreadLocal(Span)),

// For testing purposes only.
Expand Down Expand Up @@ -1341,6 +1342,7 @@ fn string_enum(
program: &mut ast::Program,
js_name: String,
generate_typescript: bool,
export_type: bool,
comments: Vec<String>,
) -> Result<(), Diagnostic> {
let mut variants = vec![];
Expand Down Expand Up @@ -1380,6 +1382,7 @@ fn string_enum(
comments,
rust_attrs: enum_.attrs,
generate_typescript,
export_type,
wasm_bindgen: program.wasm_bindgen.clone(),
}),
});
Expand Down Expand Up @@ -1453,8 +1456,6 @@ impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum {
.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 {
Expand All @@ -1469,9 +1470,22 @@ impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum {
false
});
if is_string_enum {
return string_enum(self, program, js_name, generate_typescript, comments);
let export_ype = opts.export_type().is_some();

opts.check_used();

return string_enum(
self,
program,
js_name,
generate_typescript,
export_ype,
comments,
);
}

opts.check_used();

match self.vis {
syn::Visibility::Public(_) => {}
_ => bail_span!(self, "only public enums are allowed with #[wasm_bindgen]"),
Expand Down
1 change: 1 addition & 0 deletions crates/shared/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ macro_rules! shared_api {
variant_values: Vec<&'a str>,
comments: Vec<&'a str>,
generate_typescript: bool,
export_type: bool,
}

struct Export<'a> {
Expand Down
2 changes: 1 addition & 1 deletion crates/shared/src/schema_hash_approval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "211103844299778814";
const APPROVED_SCHEMA_FILE_HASH: &str = "11946883356319465460";

#[test]
fn schema_version() {
Expand Down
1 change: 1 addition & 0 deletions guide/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
- [`getter` and `setter`](./reference/attributes/on-rust-exports/getter-and-setter.md)
- [`inspectable`](./reference/attributes/on-rust-exports/inspectable.md)
- [`skip_typescript`](./reference/attributes/on-rust-exports/skip_typescript.md)
- [`export_type`](./reference/attributes/on-rust-exports/export_type.md)
- [`getter_with_clone`](./reference/attributes/on-rust-exports/getter_with_clone.md)

- [`web-sys`](./web-sys/index.md)
Expand Down
84 changes: 84 additions & 0 deletions guide/src/reference/attributes/on-rust-exports/export_type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# `export_type`

> **Note:** This attribute is only available in `wasmbindgen` version 0.2.96 and later.

By default, string enums do not generate any publicly exported runtime bindings and type definitions. This is done because libraries like `web-sys` can use string enums to represent specific JavaScript strings values in DOM APIs (example). If string enums were publicly exported by default, a few dozen string enum type definitions would be added to the public API of all crates that use `web-sys`.

However, this presents the problem that you cannot make a string enum public if you want to use it in your own public API. For example:

```rust
// your Rust code

#[wasm_bindgen]
pub enum Status {
Success = "success",
Failure = "failure",
}

#[wasm_bindgen]
pub fn get_status() -> Status {
Status::Success
}
```

```ts
// the generated TypeScript bindings

type Status = "success" | "failure";
export function get_status(): Status;
```

As you can see, a type was generated for the `Status` string enum, but the type is not publicly exported.

While crafty users can work around this by defining their own alias (e.g. as `ReturnType<typeof get_status>`), the TypeScript API is less ergonomic because `Status` is not directly available.

The `export_type` attribute can be used to override this behavior and make the string enum publicly exported:

```rust
// your Rust code

#[wasm_bindgen(export_type)]
pub enum Status {
Success = "success",
Failure = "failure",
}

#[wasm_bindgen]
pub fn get_status() -> Status {
Status::Success
}
```

```ts
// the generated TypeScript bindings

export type Status = "success" | "failure";
export function get_status(): Status;
```

## Interaction with `skip_typescript`

If you use the [`skip_typescript` attribute](./skip_typescript.md) on a string enum, the `export_type` attribute will be ignored. No type definition will be generated for the string enum.

```rust
// your Rust code

#[wasm_bindgen(skip_typescript, export_type)]
pub enum Status {
Success = "success",
Failure = "failure",
}

#[wasm_bindgen]
pub fn get_status() -> Status {
Status::Success
}
```

```ts
// the generated TypeScript bindings

export function get_status(): Status;
```

Note that this type definition has a type error, because `Status` is not defined. Using `skip_typescript` almost always requires you to define your own TypeScript types with [`typescript_custom_section`](./typescript_custom_section.md).
3 changes: 1 addition & 2 deletions guide/src/reference/attributes/on-rust-exports/index.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# `#[wasm_bindgen]` on Rust Exports

This section enumerates the attributes available for customizing bindings for
Rust functions and `struct`s exported to JavaScript.
This section enumerates the attributes available for customizing bindings for Rust functions, `struct`s, and `enum`s exported to JavaScript.
Loading