mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-03-16 02:00:51 +00:00
Merge pull request #1012 from alexcrichton/rename-exported-type
Implement support for `js_class` on exported types
This commit is contained in:
commit
ac6a230d83
@ -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,
|
||||
|
28
guide/src/reference/attributes/on-rust-exports/js_class.md
Normal file
28
guide/src/reference/attributes/on-rust-exports/js_class.md
Normal file
@ -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();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user