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

Implement support for js_class on exported types #1012

Merged
merged 1 commit into from
Nov 5, 2018
Merged
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
17 changes: 10 additions & 7 deletions crates/backend/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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>,
}
Expand Down Expand Up @@ -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());
Expand All @@ -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),
}
}
Expand Down
12 changes: 6 additions & 6 deletions crates/backend/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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(
Expand All @@ -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>
Expand All @@ -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>
Expand All @@ -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 },
},
Expand Down
4 changes: 2 additions & 2 deletions crates/backend/src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
}
Expand Down
48 changes: 32 additions & 16 deletions crates/macro-support/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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![=]>()?;
Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand All @@ -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);
Expand All @@ -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,
})
Expand Down Expand Up @@ -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,
Expand All @@ -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) => {
Expand All @@ -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,
Expand Down Expand Up @@ -828,16 +838,18 @@ 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);
}
}
Diagnostic::from_vec(errors)
}
}

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,
Expand Down Expand Up @@ -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,
Expand Down
28 changes: 28 additions & 0 deletions guide/src/reference/attributes/on-rust-exports/js_class.md
Original file line number Diff line number Diff line change
@@ -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();
```
30 changes: 30 additions & 0 deletions guide/src/reference/attributes/on-rust-exports/js_name.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
// ...
}
```
7 changes: 7 additions & 0 deletions tests/wasm/classes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
27 changes: 27 additions & 0 deletions tests/wasm/classes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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();
}