From 7ebc4286467fe08faf231939a1b9625bf7400e9f Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 22 Mar 2018 19:05:14 -0700 Subject: [PATCH] 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 --- DESIGN.md | 24 +++++++- crates/wasm-bindgen-cli-support/src/js.rs | 29 +++++----- crates/wasm-bindgen-macro/src/ast.rs | 70 ++++++++++++++++++----- crates/wasm-bindgen-macro/src/lib.rs | 22 +++---- crates/wasm-bindgen-macro/src/literal.rs | 6 +- crates/wasm-bindgen-shared/src/lib.rs | 13 +---- examples/console_log/src/lib.rs | 6 ++ examples/dom/src/lib.rs | 8 +-- tests/import-class.rs | 4 +- tests/imports.rs | 39 +++++++++++++ 10 files changed, 155 insertions(+), 66 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index 23c695a6b..8037b8a68 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -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 diff --git a/crates/wasm-bindgen-cli-support/src/js.rs b/crates/wasm-bindgen-cli-support/src/js.rs index 10ea9742a..26920a476 100644 --- a/crates/wasm-bindgen-cli-support/src/js.rs +++ b/crates/wasm-bindgen-cli-support/src/js.rs @@ -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 } } }; diff --git a/crates/wasm-bindgen-macro/src/ast.rs b/crates/wasm-bindgen-macro/src/ast.rs index 5afdff0e1..099101fe8 100644 --- a/crates/wasm-bindgen-macro/src/ast.rs +++ b/crates/wasm-bindgen-macro/src/ast.rs @@ -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> { + pub fn getter(&self) -> Option> { 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> { + pub fn setter(&self) -> Option> { 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 { + 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), - Setter(Option), + Getter(Option), + Setter(Option), 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 } )); } diff --git a/crates/wasm-bindgen-macro/src/lib.rs b/crates/wasm-bindgen-macro/src/lib.rs index 9ac26d2f8..d7209b8ca 100755 --- a/crates/wasm-bindgen-macro/src/lib.rs +++ b/crates/wasm-bindgen-macro/src/lib.rs @@ -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)] diff --git a/crates/wasm-bindgen-macro/src/literal.rs b/crates/wasm-bindgen-macro/src/literal.rs index 60067b265..00b4d177b 100644 --- a/crates/wasm-bindgen-macro/src/literal.rs +++ b/crates/wasm-bindgen-macro/src/literal.rs @@ -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())), ]) } } diff --git a/crates/wasm-bindgen-shared/src/lib.rs b/crates/wasm-bindgen-shared/src/lib.rs index f8ae5192f..4c974182a 100644 --- a/crates/wasm-bindgen-shared/src/lib.rs +++ b/crates/wasm-bindgen-shared/src/lib.rs @@ -34,6 +34,7 @@ pub enum ImportKind { #[derive(Deserialize)] pub struct ImportFunction { + pub shim: String, pub module: Option, pub catch: bool, pub method: bool, @@ -49,6 +50,7 @@ pub struct ImportFunction { pub struct ImportStatic { pub module: Option, 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}'; diff --git a/examples/console_log/src/lib.rs b/examples/console_log/src/lib.rs index 76ebd4c37..8b8d28b43 100644 --- a/examples/console_log/src/lib.rs +++ b/examples/console_log/src/lib.rs @@ -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!"); } diff --git a/examples/dom/src/lib.rs b/examples/dom/src/lib.rs index 811ed1d80..24c539f26 100644 --- a/examples/dom/src/lib.rs +++ b/examples/dom/src/lib.rs @@ -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); } diff --git a/tests/import-class.rs b/tests/import-class.rs index c9ae934b5..ed2fadf91 100644 --- a/tests/import-class.rs +++ b/tests/import-class.rs @@ -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); } diff --git a/tests/imports.rs b/tests/imports.rs index 6bcfd939e..97e270675 100644 --- a/tests/imports.rs +++ b/tests/imports.rs @@ -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(); +}