1
0
mirror of https://github.com/fluencelabs/wasm-bindgen synced 2025-03-16 18:20:51 +00:00

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 
This commit is contained in:
Alex Crichton 2018-11-05 12:29:14 -08:00
parent 3c31a32d51
commit 16d5243362
8 changed files with 142 additions and 31 deletions
crates
backend/src
macro-support/src
guide/src/reference/attributes/on-rust-exports
tests/wasm

@ -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<Ident>,
/// The struct name, in Rust, this is attached to
pub rust_class: Option<Ident>,
/// The class name in JS this is attached to
pub js_class: Option<String>,
/// The type of `self` (either `self`, `&self`, or `&mut self`)
pub method_self: Option<MethodSelf>,
/// 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<StructField>,
pub comments: Vec<String>,
}
@ -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),
}
}

@ -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 },
},

@ -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(),
}

@ -304,7 +304,11 @@ impl Parse for BindgenAttr {
}
if attr == "js_class" {
input.parse::<Token![=]>()?;
return Ok(BindgenAttr::JsClass(input.parse::<syn::LitStr>()?.value()));
let val = match input.parse::<syn::LitStr>() {
Ok(str) => str.value(),
Err(_) => input.parse::<AnyIdent>()?.0.to_string(),
};
return Ok(BindgenAttr::JsClass(val));
}
if attr == "js_name" {
input.parse::<Token![=]>()?;
@ -346,10 +350,10 @@ trait ConvertToAst<Ctx> {
fn convert(self, context: Ctx) -> Result<Self::Target, Diagnostic>;
}
impl<'a> ConvertToAst<()> for &'a mut syn::ItemStruct {
impl<'a> ConvertToAst<BindgenAttrs> for &'a mut syn::ItemStruct {
type Target = ast::Struct;
fn convert(self, (): ()) -> Result<Self::Target, Diagnostic> {
fn convert(self, opts: BindgenAttrs) -> Result<Self::Target, Diagnostic> {
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<String> = 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<BindgenAttrs>, &'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<BindgenAttrs>, &'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<BindgenAttrs>, &'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<BindgenAttrs> 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,

@ -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();
```

@ -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 {
// ...
}
```

@ -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);
};

@ -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();
}