From 16d5243362df82aa63018a8ea39344f79f79b780 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 5 Nov 2018 12:29:14 -0800 Subject: [PATCH] Implement support for `js_class` on exported types Allow defining types which have different names in Rust than they have in JS! (just like can be done with imported types) Closes #1010 --- crates/backend/src/ast.rs | 17 ++++--- crates/backend/src/codegen.rs | 12 ++--- crates/backend/src/encode.rs | 4 +- crates/macro-support/src/parser.rs | 48 ++++++++++++------- .../attributes/on-rust-exports/js_class.md | 28 +++++++++++ .../attributes/on-rust-exports/js_name.md | 30 ++++++++++++ tests/wasm/classes.js | 7 +++ tests/wasm/classes.rs | 27 +++++++++++ 8 files changed, 142 insertions(+), 31 deletions(-) create mode 100644 guide/src/reference/attributes/on-rust-exports/js_class.md diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index ff6f3db1..6dcf799a 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -29,8 +29,10 @@ pub struct Program { #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[derive(Clone)] pub struct Export { - /// The javascript class name. - pub class: Option, + /// The struct name, in Rust, this is attached to + pub rust_class: Option, + /// The class name in JS this is attached to + pub js_class: Option, /// The type of `self` (either `self`, `&self`, or `&mut self`) pub method_self: Option, /// Whether or not this export is flagged as a constructor, returning an @@ -176,7 +178,8 @@ pub struct Function { #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[derive(Clone)] pub struct Struct { - pub name: Ident, + pub rust_name: Ident, + pub js_name: String, pub fields: Vec, pub comments: Vec, } @@ -264,9 +267,9 @@ impl Export { /// name and class name, if the function belongs to a javascript class. pub(crate) fn rust_symbol(&self) -> Ident { let mut generated_name = String::from("__wasm_bindgen_generated"); - if let Some(class) = &self.class { + if let Some(class) = &self.js_class { generated_name.push_str("_"); - generated_name.push_str(&class.to_string()); + generated_name.push_str(class); } generated_name.push_str("_"); generated_name.push_str(&self.function.name.to_string()); @@ -278,8 +281,8 @@ impl Export { /// "high level" form before calling the actual function. pub(crate) fn export_name(&self) -> String { let fn_name = self.function.name.to_string(); - match &self.class { - Some(class) => shared::struct_function_export_name(&class.to_string(), &fn_name), + match &self.js_class { + Some(class) => shared::struct_function_export_name(class, &fn_name), None => shared::free_function_export_name(&fn_name), } } diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index c9d1f63c..3df58c40 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -118,8 +118,8 @@ impl TryToTokens for ast::Program { impl ToTokens for ast::Struct { fn to_tokens(&self, tokens: &mut TokenStream) { - let name = &self.name; - let name_str = name.to_string(); + let name = &self.rust_name; + let name_str = self.js_name.to_string(); let name_len = name_str.len() as u32; let name_chars = name_str.chars().map(|c| c as u32); let new_fn = Ident::new(&shared::new_function(&name_str), Span::call_site()); @@ -328,7 +328,7 @@ impl TryToTokens for ast::Export { let name = &self.rust_name; let receiver = match self.method_self { Some(ast::MethodSelf::ByValue) => { - let class = self.class.as_ref().unwrap(); + let class = self.rust_class.as_ref().unwrap(); arg_conversions.push(quote! { let me = unsafe { <#class as ::wasm_bindgen::convert::FromWasmAbi>::from_abi( @@ -340,7 +340,7 @@ impl TryToTokens for ast::Export { quote! { me.#name } } Some(ast::MethodSelf::RefMutable) => { - let class = self.class.as_ref().unwrap(); + let class = self.rust_class.as_ref().unwrap(); arg_conversions.push(quote! { let mut me = unsafe { <#class as ::wasm_bindgen::convert::RefMutFromWasmAbi> @@ -354,7 +354,7 @@ impl TryToTokens for ast::Export { quote! { me.#name } } Some(ast::MethodSelf::RefShared) => { - let class = self.class.as_ref().unwrap(); + let class = self.rust_class.as_ref().unwrap(); arg_conversions.push(quote! { let me = unsafe { <#class as ::wasm_bindgen::convert::RefFromWasmAbi> @@ -367,7 +367,7 @@ impl TryToTokens for ast::Export { }); quote! { me.#name } } - None => match &self.class { + None => match &self.rust_class { Some(class) => quote! { #class::#name }, None => quote! { #name }, }, diff --git a/crates/backend/src/encode.rs b/crates/backend/src/encode.rs index 04d92436..b82ca2a7 100644 --- a/crates/backend/src/encode.rs +++ b/crates/backend/src/encode.rs @@ -58,7 +58,7 @@ fn shared_export<'a>(export: &'a ast::Export, intern: &'a Interner) -> Export<'a None => (false, false), }; Export { - class: export.class.as_ref().map(|s| intern.intern(s)), + class: export.js_class.as_ref().map(|s| &**s), method, consumed, is_constructor: export.is_constructor, @@ -187,7 +187,7 @@ fn shared_import_enum<'a>(_i: &'a ast::ImportEnum, _intern: &'a Interner) fn shared_struct<'a>(s: &'a ast::Struct, intern: &'a Interner) -> Struct<'a> { Struct { - name: intern.intern(&s.name), + name: &s.js_name, fields: s.fields.iter().map(|s| shared_struct_field(s, intern)).collect(), comments: s.comments.iter().map(|s| &**s).collect(), } diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 459bbd1f..23dc483f 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -304,7 +304,11 @@ impl Parse for BindgenAttr { } if attr == "js_class" { input.parse::()?; - return Ok(BindgenAttr::JsClass(input.parse::()?.value())); + let val = match input.parse::() { + Ok(str) => str.value(), + Err(_) => input.parse::()?.0.to_string(), + }; + return Ok(BindgenAttr::JsClass(val)); } if attr == "js_name" { input.parse::()?; @@ -346,10 +350,10 @@ trait ConvertToAst { fn convert(self, context: Ctx) -> Result; } -impl<'a> ConvertToAst<()> for &'a mut syn::ItemStruct { +impl<'a> ConvertToAst for &'a mut syn::ItemStruct { type Target = ast::Struct; - fn convert(self, (): ()) -> Result { + fn convert(self, opts: BindgenAttrs) -> Result { if self.generics.params.len() > 0 { bail_span!( self.generics, @@ -358,6 +362,9 @@ impl<'a> ConvertToAst<()> for &'a mut syn::ItemStruct { ); } let mut fields = Vec::new(); + let js_name = opts.js_name() + .map(|s| s.0.to_string()) + .unwrap_or(self.ident.to_string()); if let syn::Fields::Named(names) = &mut self.fields { for field in names.named.iter_mut() { match field.vis { @@ -368,10 +375,9 @@ impl<'a> ConvertToAst<()> for &'a mut syn::ItemStruct { Some(n) => n, None => continue, }; - let ident = self.ident.to_string(); let name_str = name.to_string(); - let getter = shared::struct_field_get(&ident, &name_str); - let setter = shared::struct_field_set(&ident, &name_str); + let getter = shared::struct_field_get(&js_name, &name_str); + let setter = shared::struct_field_set(&js_name, &name_str); let opts = BindgenAttrs::find(&mut field.attrs)?; assert_not_variadic(&opts, &field)?; let comments = extract_doc_comments(&field.attrs); @@ -388,7 +394,8 @@ impl<'a> ConvertToAst<()> for &'a mut syn::ItemStruct { } let comments: Vec = extract_doc_comments(&self.attrs); Ok(ast::Struct { - name: self.ident.clone(), + rust_name: self.ident.clone(), + js_name, fields, comments, }) @@ -755,7 +762,8 @@ impl<'a> MacroParse<(Option, &'a mut TokenStream)> for syn::Item { f.to_tokens(tokens); let opts = opts.unwrap_or_default(); program.exports.push(ast::Export { - class: None, + rust_class: None, + js_class: None, method_self: None, is_constructor: false, comments, @@ -764,11 +772,13 @@ impl<'a> MacroParse<(Option, &'a mut TokenStream)> for syn::Item { }); } syn::Item::Struct(mut s) => { - program.structs.push((&mut s).convert(())?); + let opts = opts.unwrap_or_default(); + program.structs.push((&mut s).convert(opts)?); s.to_tokens(tokens); } syn::Item::Impl(mut i) => { - (&mut i).macro_parse(program, ())?; + let opts = opts.unwrap_or_default(); + (&mut i).macro_parse(program, opts)?; i.to_tokens(tokens); } syn::Item::ForeignMod(mut f) => { @@ -793,8 +803,8 @@ impl<'a> MacroParse<(Option, &'a mut TokenStream)> for syn::Item { } } -impl<'a> MacroParse<()> for &'a mut syn::ItemImpl { - fn macro_parse(self, program: &mut ast::Program, (): ()) -> Result<(), Diagnostic> { +impl<'a> MacroParse for &'a mut syn::ItemImpl { + fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> { if self.defaultness.is_some() { bail_span!( self.defaultness, @@ -828,7 +838,7 @@ impl<'a> MacroParse<()> for &'a mut syn::ItemImpl { }; let mut errors = Vec::new(); for item in self.items.iter_mut() { - if let Err(e) = (&name, item).macro_parse(program, ()) { + if let Err(e) = (&name, item).macro_parse(program, &opts) { errors.push(e); } } @@ -836,8 +846,10 @@ impl<'a> MacroParse<()> for &'a mut syn::ItemImpl { } } -impl<'a, 'b> MacroParse<()> for (&'a Ident, &'b mut syn::ImplItem) { - fn macro_parse(self, program: &mut ast::Program, (): ()) -> Result<(), Diagnostic> { +impl<'a, 'b> MacroParse<&'a BindgenAttrs> for (&'a Ident, &'b mut syn::ImplItem) { + fn macro_parse(self, program: &mut ast::Program, impl_opts: &'a BindgenAttrs) + -> Result<(), Diagnostic> + { let (class, item) = self; let method = match item { syn::ImplItem::Method(ref mut m) => m, @@ -889,9 +901,13 @@ impl<'a, 'b> MacroParse<()> for (&'a Ident, &'b mut syn::ImplItem) { true, Some(class), )?; + let js_class = impl_opts.js_class() + .map(|s| s.to_string()) + .unwrap_or(class.to_string()); program.exports.push(ast::Export { - class: Some(class.clone()), + rust_class: Some(class.clone()), + js_class: Some(js_class), method_self, is_constructor, function, diff --git a/guide/src/reference/attributes/on-rust-exports/js_class.md b/guide/src/reference/attributes/on-rust-exports/js_class.md new file mode 100644 index 00000000..1952b1e3 --- /dev/null +++ b/guide/src/reference/attributes/on-rust-exports/js_class.md @@ -0,0 +1,28 @@ +# `js_class = Blah` + +The `js_class` attribute is used to indicate that all the methods inside an +`impl` block should be attached to the specified JS class instead of inferring +it from the self type in the `impl` block. The `js_class` attribute is most +frequently paired with [the `js_name` attribute](js_name.html) on structs: + +```rust +#[wasm_bindgen(js_name = Foo)] +pub struct JsFoo { /* ... */ } + +#[wasm_bindgen(js_class = Foo)] +impl JsFoo { + #[wasm_bindgen(constructor)] + pub fn new() -> JsFoo { /* ... */ } + + pub fn foo(&self) { /* ... */ } +} +``` + +which is accessed like: + +```rust +import { Foo } from './my_module'; + +const x = new Foo(); +x.foo(); +``` diff --git a/guide/src/reference/attributes/on-rust-exports/js_name.md b/guide/src/reference/attributes/on-rust-exports/js_name.md index 564d8cd0..b6a88385 100644 --- a/guide/src/reference/attributes/on-rust-exports/js_name.md +++ b/guide/src/reference/attributes/on-rust-exports/js_name.md @@ -22,3 +22,33 @@ import { doTheThing } from './my_module'; const x = doTheThing(); console.log(x); ``` + +Like imports, `js_name` can also be used to rename types exported to JS: + +```rust +#[wasm_bindgen(js_name = Foo)] +pub struct JsFoo { + // .. +} +``` + +to be accessed like: + +```js +import { Foo } from './my_module'; + +// ... +``` + +Note that attaching methods to the JS class `Foo` should be done via the +[`js_class` attribute](js_class.html): + +```rust +#[wasm_bindgen(js_name = Foo)] +pub struct JsFoo { /* ... */ } + +#[wasm_bindgen(js_class = Foo)] +impl JsFoo { + // ... +} +``` diff --git a/tests/wasm/classes.js b/tests/wasm/classes.js index 4e0408bf..1b47d378 100644 --- a/tests/wasm/classes.js +++ b/tests/wasm/classes.js @@ -136,3 +136,10 @@ exports.js_js_rename = () => { exports.js_access_fields = () => { assert.ok((new wasm.AccessFieldFoo()).bar instanceof wasm.AccessFieldBar); }; + +exports.js_renamed_export = () => { + const x = new wasm.JsRenamedExport(); + assert.ok(x.x === 3); + x.foo(); + x.bar(x); +}; diff --git a/tests/wasm/classes.rs b/tests/wasm/classes.rs index b01763d7..10b14009 100644 --- a/tests/wasm/classes.rs +++ b/tests/wasm/classes.rs @@ -21,6 +21,7 @@ extern "C" { fn js_double_consume(); fn js_js_rename(); fn js_access_fields(); + fn js_renamed_export(); } #[wasm_bindgen_test] @@ -379,3 +380,29 @@ impl AccessFieldFoo { fn access_fields() { js_access_fields(); } + +#[wasm_bindgen(js_name = JsRenamedExport)] +pub struct RenamedExport { + pub x: u32, +} + +#[wasm_bindgen(js_class = JsRenamedExport)] +impl RenamedExport { + #[wasm_bindgen(constructor)] + pub fn new() -> RenamedExport{ + RenamedExport { + x: 3, + } + } + pub fn foo(&self) { + } + + pub fn bar(&self, other: &RenamedExport) { + drop(other); + } +} + +#[wasm_bindgen_test] +fn renamed_export() { + js_renamed_export(); +}