Implement a js_name customization

This'll allow binding multiple signatures of a JS function as well as otherwise
changing the name of the JS function you're calling from the Rust function that
you're defining.

Closes #72
This commit is contained in:
Alex Crichton 2018-03-22 19:05:14 -07:00
parent a8045fbbe9
commit 7ebc428646
10 changed files with 155 additions and 66 deletions

View File

@ -975,7 +975,7 @@ possibilities!
accessible as `foo.set_property(2)`. Note that both functions have a `this`
argument as they're tagged with `method`.
Finally, you can also pass a string argument to the `getter` and `setter`
Finally, you can also pass an argument to the `getter` and `setter`
properties to configure what property is accessed. When the property is
explicitly specified then there is no restriction on the method name. For
example the below is equivalent to the above:
@ -984,7 +984,7 @@ possibilities!
#[wasm_bindgen]
extern {
type Foo;
#[wasm_bindgen(method, getter = "property")]
#[wasm_bindgen(method, getter = property)]
fn assorted_method_name(this: &Foo) -> u32;
#[wasm_bindgen(method, setter = "property")]
fn some_other_method_name(this: &Foo, val: u32);
@ -1017,6 +1017,26 @@ possibilities!
Instead wasm-bindgen will generate shims that will access the passed in JS
value's `bar` property to or the `baz` property (depending on the function).
* `js_name = foo` - this can be used to bind to a different function in JS than
the identifier that's defined in Rust. For example you can also define
multiple signatures for a polymorphic function in JS as well:
```rust
#[wasm_bindgen]
extern {
type Foo;
#[wasm_bindgen(js_namespace = console, js_name = log)]
fn log_string(s: &str);
#[wasm_bindgen(js_namespace = console, js_name = log)]
fn log_u32(n: u32);
#[wasm_bindgen(js_namespace = console, js_name = log)]
fn log_many(a: u32, b: JsValue);
}
```
All of these functions will call `console.log` in Rust, but each identifier
will have only one signature in Rust.
## Wrapping up
That's currently at least what `wasm-bindgen` has to offer! If you've got more

View File

@ -1336,27 +1336,24 @@ impl<'a, 'b> SubContext<'a, 'b> {
info: &shared::Import,
import: &shared::ImportStatic) {
// TODO: should support more types to import here
let name = shared::static_import_shim_name(&import.name);
self.cx.imports_to_rewrite.insert(name.clone());
self.cx.imports_to_rewrite.insert(import.shim.clone());
let obj = self.import_name(info, &import.name);
self.cx.expose_add_heap_object();
self.cx.globals.push_str(&format!("
export function {}() {{
return addHeapObject({});
}}
", name, obj));
", import.shim, obj));
}
pub fn generate_import_function(&mut self,
info: &shared::Import,
import: &shared::ImportFunction) {
let name = shared::mangled_import_name(import.class.as_ref().map(|s| &**s),
&import.function.name);
self.cx.imports_to_rewrite.insert(name.clone());
self.cx.imports_to_rewrite.insert(import.shim.clone());
let mut dst = String::new();
dst.push_str(&format!("function {}(", name));
dst.push_str(&format!("function {}(", import.shim));
let mut invoc_args = Vec::new();
let mut abi_args = Vec::new();
@ -1490,25 +1487,25 @@ impl<'a, 'b> SubContext<'a, 'b> {
};
self.cx.globals.push_str(&format!("
const {}_target = {};
", name, target));
format!("{}_target.call", name)
", import.shim, target));
format!("{}_target.call", import.shim)
}
Some(ref class) => {
let class = self.import_name(info, class);
self.cx.globals.push_str(&format!("
const {}_target = {}.{};
", name, class, function_name));
format!("{}_target", name)
", import.shim, class, function_name));
format!("{}_target", import.shim)
}
None => {
let import = self.import_name(info, function_name);
if import.contains(".") {
let name = self.import_name(info, function_name);
if name.contains(".") {
self.cx.globals.push_str(&format!("
const {}_target = {};
", name, import));
format!("{}_target", name)
", import.shim, name));
format!("{}_target", import.shim)
} else {
import
name
}
}
};

View File

@ -33,7 +33,9 @@ pub enum ImportKind {
pub struct ImportFunction {
pub function: Function,
pub rust_name: syn::Ident,
pub kind: ImportFunctionKind,
pub shim: syn::Ident,
}
pub enum ImportFunctionKind {
@ -51,7 +53,9 @@ pub enum ImportFunctionKind {
pub struct ImportStatic {
pub vis: syn::Visibility,
pub ty: syn::Type,
pub name: syn::Ident,
pub shim: syn::Ident,
pub rust_name: syn::Ident,
pub js_name: syn::Ident,
}
pub struct ImportType {
@ -273,7 +277,7 @@ impl Program {
let mut kind = match item {
syn::ForeignItem::Fn(f) => self.push_foreign_fn(f, item_opts),
syn::ForeignItem::Type(t) => self.push_foreign_ty(t),
syn::ForeignItem::Static(s) => self.push_foreign_static(s),
syn::ForeignItem::Static(s) => self.push_foreign_static(s, item_opts),
_ => panic!("only foreign functions/types allowed for now"),
};
@ -284,7 +288,8 @@ impl Program {
pub fn push_foreign_fn(&mut self, f: syn::ForeignItemFn, opts: BindgenAttrs)
-> ImportKind
{
let mut wasm = Function::from_decl(f.ident,
let js_name = opts.js_name().unwrap_or(f.ident);
let mut wasm = Function::from_decl(js_name,
f.decl,
f.attrs,
opts,
@ -346,9 +351,19 @@ impl Program {
ImportFunctionKind::Normal
};
let shim = {
let ns = match kind {
ImportFunctionKind::Normal => "n",
ImportFunctionKind::Method { ref class, .. } => class,
ImportFunctionKind::JsConstructor { ref class, .. } => class,
};
format!("__wbg_f_{}_{}_{}", js_name, f.ident, ns)
};
ImportKind::Function(ImportFunction {
function: wasm,
kind,
rust_name: f.ident,
shim: shim.into(),
})
}
@ -361,16 +376,22 @@ impl Program {
})
}
pub fn push_foreign_static(&mut self, f: syn::ForeignItemStatic)
pub fn push_foreign_static(&mut self,
f: syn::ForeignItemStatic,
opts: BindgenAttrs)
-> ImportKind
{
if f.mutability.is_some() {
panic!("cannot import mutable globals yet")
}
let js_name = opts.js_name().unwrap_or(f.ident);
let shim = format!("__wbg_static_accessor_{}_{}", js_name, f.ident);
ImportKind::Static(ImportStatic {
ty: *f.ty,
vis: f.vis,
name: f.ident,
rust_name: f.ident,
js_name,
shim: shim.into(),
})
}
@ -651,22 +672,22 @@ impl BindgenAttrs {
.next()
}
pub fn getter(&self) -> Option<Option<String>> {
pub fn getter(&self) -> Option<Option<syn::Ident>> {
self.attrs.iter()
.filter_map(|a| {
match *a {
BindgenAttr::Getter(ref s) => Some(s.clone()),
BindgenAttr::Getter(s) => Some(s),
_ => None,
}
})
.next()
}
pub fn setter(&self) -> Option<Option<String>> {
pub fn setter(&self) -> Option<Option<syn::Ident>> {
self.attrs.iter()
.filter_map(|a| {
match *a {
BindgenAttr::Setter(ref s) => Some(s.clone()),
BindgenAttr::Setter(s) => Some(s),
_ => None,
}
})
@ -682,6 +703,17 @@ impl BindgenAttrs {
}
})
}
pub fn js_name(&self) -> Option<syn::Ident> {
self.attrs.iter()
.filter_map(|a| {
match *a {
BindgenAttr::JsName(s) => Some(s),
_ => None,
}
})
.next()
}
}
impl syn::synom::Synom for BindgenAttrs {
@ -705,9 +737,10 @@ enum BindgenAttr {
Method,
JsNamespace(syn::Ident),
Module(String),
Getter(Option<String>),
Setter(Option<String>),
Getter(Option<syn::Ident>),
Setter(Option<syn::Ident>),
Structural,
JsName(syn::Ident),
}
impl syn::synom::Synom for BindgenAttr {
@ -722,8 +755,8 @@ impl syn::synom::Synom for BindgenAttr {
call!(term, "getter") >>
val: option!(do_parse!(
punct!(=) >>
s: syn!(syn::LitStr) >>
(s.value())
s: syn!(syn::Ident) >>
(s)
)) >>
(val)
)=> { BindgenAttr::Getter }
@ -732,8 +765,8 @@ impl syn::synom::Synom for BindgenAttr {
call!(term, "setter") >>
val: option!(do_parse!(
punct!(=) >>
s: syn!(syn::LitStr) >>
(s.value())
s: syn!(syn::Ident) >>
(s)
)) >>
(val)
)=> { BindgenAttr::Setter }
@ -753,6 +786,13 @@ impl syn::synom::Synom for BindgenAttr {
s: syn!(syn::LitStr) >>
(s.value())
)=> { BindgenAttr::Module }
|
do_parse!(
call!(term, "js_name") >>
punct!(=) >>
ns: syn!(syn::Ident) >>
(ns)
)=> { BindgenAttr::JsName }
));
}

View File

@ -388,23 +388,16 @@ impl ToTokens for ast::ImportFunction {
fn to_tokens(&self, tokens: &mut Tokens) {
let mut class_ty = None;
let mut is_method = false;
let mut class_name = None;
match self.kind {
ast::ImportFunctionKind::Method { ref ty, ref class } => {
ast::ImportFunctionKind::Method { ref ty, .. } => {
is_method = true;
class_ty = Some(ty);
class_name = Some(class);
}
ast::ImportFunctionKind::JsConstructor { ref ty, ref class } => {
ast::ImportFunctionKind::JsConstructor { ref ty, .. } => {
class_ty = Some(ty);
class_name = Some(class);
}
ast::ImportFunctionKind::Normal => {}
}
let import_name = shared::mangled_import_name(
class_name.map(|s| &**s),
self.function.name.as_ref(),
);
let vis = &self.function.rust_vis;
let ret = &self.function.rust_decl.output;
let fn_token = &self.function.rust_decl.fn_token;
@ -552,8 +545,8 @@ impl ToTokens for ast::ImportFunction {
};
}
let name = self.function.name;
let import_name = syn::Ident::from(import_name);
let rust_name = self.rust_name;
let import_name = self.shim;
let attrs = &self.function.rust_attrs;
let arguments = self.function.rust_decl.inputs
@ -570,7 +563,7 @@ impl ToTokens for ast::ImportFunction {
let invocation = my_quote! {
#(#attrs)*
#[allow(bad_style)]
#vis extern #fn_token #name(#me #(#arguments),*) #ret {
#vis extern #fn_token #rust_name(#me #(#arguments),*) #ret {
::wasm_bindgen::__rt::link_this_library();
extern {
fn #import_name(#(#abi_arguments),*) -> #abi_ret;
@ -637,10 +630,9 @@ impl ToTokens for ast::Enum {
impl ToTokens for ast::ImportStatic {
fn to_tokens(&self, into: &mut Tokens) {
let name = self.name;
let name = self.rust_name;
let ty = &self.ty;
let shim_name = shared::static_import_shim_name(name.as_ref());
let shim_name = syn::Ident::from(shim_name);
let shim_name = self.shim;
let vis = &self.vis;
(my_quote! {
#[allow(bad_style)]

View File

@ -245,9 +245,11 @@ impl Literal for ast::ImportFunction {
let structural = self.function.opts.structural();
if let Some(s) = self.function.opts.getter() {
let s = s.map(|s| s.to_string());
getter = Some(s.unwrap_or_else(|| self.infer_getter_property()));
}
if let Some(s) = self.function.opts.setter() {
let s = s.map(|s| s.to_string());
setter = Some(s.unwrap_or_else(|| self.infer_setter_property()));
}
a.fields(&[
@ -256,6 +258,7 @@ impl Literal for ast::ImportFunction {
("method", &|a| a.bool(method)),
("js_new", &|a| a.bool(js_new)),
("structural", &|a| a.bool(structural)),
("shim", &|a| a.str(self.shim.as_ref())),
("getter", &|a| match getter {
Some(ref s) => a.str(s),
None => a.append("null"),
@ -295,7 +298,8 @@ impl Literal for ast::ImportStatic {
fn literal(&self, a: &mut LiteralBuilder) {
a.fields(&[
("kind", &|a| a.str("static")),
("name", &|a| a.str(self.name.as_ref())),
("name", &|a| a.str(self.js_name.as_ref())),
("shim", &|a| a.str(self.shim.as_ref())),
])
}
}

View File

@ -34,6 +34,7 @@ pub enum ImportKind {
#[derive(Deserialize)]
pub struct ImportFunction {
pub shim: String,
pub module: Option<String>,
pub catch: bool,
pub method: bool,
@ -49,6 +50,7 @@ pub struct ImportFunction {
pub struct ImportStatic {
pub module: Option<String>,
pub name: String,
pub shim: String,
}
#[derive(Deserialize)]
@ -110,17 +112,6 @@ pub fn struct_function_export_name(struct_: &str, f: &str) -> String {
return name
}
pub fn mangled_import_name(struct_: Option<&str>, f: &str) -> String {
match struct_ {
Some(s) => format!("__wbg_s_{}_{}", s, f),
None => format!("__wbg_f_{}", f),
}
}
pub fn static_import_shim_name(statik: &str) -> String {
format!("__wbg_field_import_shim_{}", statik)
}
pub type Type = char;
pub const TYPE_VECTOR_JSVALUE: char = '\u{5b}';

View File

@ -8,9 +8,15 @@ use wasm_bindgen::prelude::*;
extern {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
#[wasm_bindgen(js_namespace = console, js_name = log)]
fn log_u32(a: u32);
#[wasm_bindgen(js_namespace = console, js_name = log)]
fn log_many(a: &str, b: &str);
}
#[wasm_bindgen]
pub fn run() {
log("Hello from Rust!");
log_u32(42);
log_many("Logging", "many values!");
}

View File

@ -24,10 +24,10 @@ extern {
fn body(this: &HTMLDocument) -> Element;
type Element;
#[wasm_bindgen(method, setter = "innerHTML")]
#[wasm_bindgen(method, setter = innerHTML)]
fn set_inner_html(this: &Element, html: &str);
#[wasm_bindgen(method)]
fn appendChild(this: &Element, other: Element);
#[wasm_bindgen(method, js_name = appendChild)]
fn append_child(this: &Element, other: Element);
}
// Called by our JS entry point to run the example
@ -35,5 +35,5 @@ extern {
pub fn run() {
let val = document.createElement("p");
val.set_inner_html("Hello from Rust!");
document.body().appendChild(val);
document.body().append_child(val);
}

View File

@ -353,10 +353,10 @@ fn rename_setter_getter() {
#[wasm_bindgen(constructor)]
fn new() -> Foo;
#[wasm_bindgen(getter = "a", method)]
#[wasm_bindgen(getter = a, method)]
fn test(this: &Foo) -> i32;
#[wasm_bindgen(setter = "a", method)]
#[wasm_bindgen(setter = a, method)]
fn another(this: &Foo, a: i32);
}

View File

@ -310,3 +310,42 @@ fn import_a_field() {
"#)
.test();
}
#[test]
fn rename() {
test_support::project()
.file("src/lib.rs", r#"
#![feature(proc_macro)]
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen(module = "./test")]
extern {
#[wasm_bindgen(js_name = baz)]
fn foo();
}
#[wasm_bindgen]
pub fn run() {
foo();
}
"#)
.file("test.ts", r#"
import * as wasm from "./out";
import * as assert from "assert";
let called = false;
export function baz() {
called = true;
}
export function test() {
wasm.run();
assert.strictEqual(called, true);
}
"#)
.test();
}