From f4eef6d8fc6b971b2e615898ad66822eb24a7db8 Mon Sep 17 00:00:00 2001 From: Hajime Fukuda Date: Wed, 27 May 2020 08:36:20 -0700 Subject: [PATCH] Enable nested namespace (#951) (#2105) * Enable nested namespace (#951) * Specify the namespace as array (#951) * added an example to the document Co-authored-by: Alex Crichton --- crates/backend/src/ast.rs | 2 +- crates/backend/src/codegen.rs | 31 ++++++------ crates/backend/src/encode.rs | 2 +- crates/cli-support/src/js/mod.rs | 2 +- crates/cli-support/src/wit/mod.rs | 10 ++-- crates/macro-support/Cargo.toml | 2 +- crates/macro-support/src/parser.rs | 50 ++++++++++++++++++- crates/shared/src/lib.rs | 2 +- .../attributes/on-js-imports/js_namespace.md | 15 ++++++ tests/wasm/import_class.js | 28 +++++++++++ tests/wasm/import_class.rs | 24 +++++++++ 11 files changed, 144 insertions(+), 24 deletions(-) diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 603253e663ca..222bd10b823a 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -75,7 +75,7 @@ pub enum MethodSelf { #[derive(Clone)] pub struct Import { pub module: ImportModule, - pub js_namespace: Option, + pub js_namespace: Option>, pub kind: ImportKind, } diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 8ce361298845..ad982ffa1a4f 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -4,7 +4,7 @@ use crate::util::ShortHash; use crate::Diagnostic; use proc_macro2::{Ident, Literal, Span, TokenStream}; use quote::{quote, ToTokens}; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Mutex; use syn; @@ -32,10 +32,10 @@ impl TryToTokens for ast::Program { for s in self.structs.iter() { s.to_tokens(tokens); } - let mut types = HashSet::new(); + let mut types = HashMap::new(); for i in self.imports.iter() { if let ast::ImportKind::Type(t) = &i.kind { - types.insert(t.rust_name.clone()); + types.insert(t.rust_name.to_string(), t.rust_name.clone()); } } for i in self.imports.iter() { @@ -43,17 +43,20 @@ impl TryToTokens for ast::Program { // If there is a js namespace, check that name isn't a type. If it is, // this import might be a method on that type. - if let Some(ns) = &i.js_namespace { - if types.contains(ns) && i.kind.fits_on_impl() { - let kind = match i.kind.try_to_token_stream() { - Ok(kind) => kind, - Err(e) => { - errors.push(e); - continue; - } - }; - (quote! { impl #ns { #kind } }).to_tokens(tokens); - continue; + if let Some(nss) = &i.js_namespace { + // When the namespace is `A.B`, the type name should be `B`. + if let Some(ns) = nss.last().and_then(|t| types.get(t)) { + if i.kind.fits_on_impl() { + let kind = match i.kind.try_to_token_stream() { + Ok(kind) => kind, + Err(e) => { + errors.push(e); + continue; + } + }; + (quote! { impl #ns { #kind } }).to_tokens(tokens); + continue; + } } } diff --git a/crates/backend/src/encode.rs b/crates/backend/src/encode.rs index 4728eb2088e6..38f2c04a7831 100644 --- a/crates/backend/src/encode.rs +++ b/crates/backend/src/encode.rs @@ -241,7 +241,7 @@ fn shared_import<'a>(i: &'a ast::Import, intern: &'a Interner) -> Result ImportModule::Inline(*idx as u32), ast::ImportModule::None => ImportModule::None, }, - js_namespace: i.js_namespace.as_ref().map(|s| intern.intern(s)), + js_namespace: i.js_namespace.clone(), kind: shared_import_kind(&i.kind, intern)?, }) } diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index f5030a642e86..005f2eab4600 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1998,7 +1998,7 @@ impl<'a> Context<'a> { JsImportName::VendorPrefixed { name, prefixes } => { self.imports_post.push_str("const l"); - self.imports_post.push_str(&name); + self.imports_post.push_str(name); self.imports_post.push_str(" = "); switch(&mut self.imports_post, name, "", prefixes); self.imports_post.push_str(";\n"); diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index ce82efdf13d3..68f591ea5bc5 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -898,7 +898,7 @@ impl<'a> Context<'a> { "import of `{}` through js namespace `{}` isn't supported \ right now when it lists a polyfill", item, - ns + ns.join(".") ); } return Ok(JsImport { @@ -911,8 +911,12 @@ impl<'a> Context<'a> { } let (name, fields) = match import.js_namespace { - Some(ns) => (ns, vec![item.to_string()]), - None => (item, Vec::new()), + Some(ref ns) => { + let mut tail = (&ns[1..]).to_owned(); + tail.push(item.to_string()); + (ns[0].to_owned(), tail) + } + None => (item.to_owned(), Vec::new()), }; let name = match import.module { diff --git a/crates/macro-support/Cargo.toml b/crates/macro-support/Cargo.toml index c66f8a52bef1..f5fba4dfbe3f 100644 --- a/crates/macro-support/Cargo.toml +++ b/crates/macro-support/Cargo.toml @@ -17,7 +17,7 @@ extra-traits = ["syn/extra-traits"] strict-macro = [] [dependencies] -syn = { version = '1.0.27', features = ['visit'] } +syn = { version = '1.0.27', features = ['visit', 'full'] } quote = '1.0' proc-macro2 = "1.0" wasm-bindgen-backend = { path = "../backend", version = "=0.2.63" } diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index bb64914a5ef2..62c1388963e3 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -11,6 +11,7 @@ use quote::ToTokens; use shared; use syn; use syn::parse::{Parse, ParseStream, Result as SynResult}; +use syn::spanned::Spanned; thread_local!(static ATTRS: AttributeParseState = Default::default()); @@ -34,7 +35,7 @@ macro_rules! attrgen { (constructor, Constructor(Span)), (method, Method(Span)), (static_method_of, StaticMethodOf(Span, Ident)), - (js_namespace, JsNamespace(Span, Ident)), + (js_namespace, JsNamespace(Span, Vec, Vec)), (module, Module(Span, String, Span)), (raw_module, RawModule(Span, String, Span)), (inline_js, InlineJs(Span, String, Span)), @@ -116,6 +117,21 @@ macro_rules! methods { } }; + (@method $name:ident, $variant:ident(Span, Vec, Vec)) => { + fn $name(&self) -> Option<(&[String], &[Span])> { + self.attrs + .iter() + .filter_map(|a| match &a.1 { + BindgenAttr::$variant(_, ss, spans) => { + a.0.set(true); + Some((&ss[..], &spans[..])) + } + _ => None, + }) + .next() + } + }; + (@method $name:ident, $variant:ident(Span, $($other:tt)*)) => { #[allow(unused)] fn $name(&self) -> Option<&$($other)*> { @@ -280,6 +296,36 @@ impl Parse for BindgenAttr { }; return Ok(BindgenAttr::$variant(attr_span, val, span)) }); + + (@parser $variant:ident(Span, Vec, Vec)) => ({ + input.parse::()?; + let input_before_parse = input.fork(); + let (vals, spans) = match input.parse::() { + Ok(exprs) => { + let mut vals = vec![]; + let mut spans = vec![]; + + for expr in exprs.elems.iter() { + if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(ref str), + .. + }) = expr { + vals.push(str.value()); + spans.push(str.span()); + } else { + return Err(syn::Error::new(expr.span(), "expected string literals")); + } + } + + (vals, spans) + }, + Err(_) => { + let ident = input_before_parse.parse::()?.0; + (vec![ident.to_string()], vec![ident.span()]) + } + }; + return Ok(BindgenAttr::$variant(attr_span, vals, spans)) + }); } attrgen!(parsers); @@ -1270,7 +1316,7 @@ impl MacroParse for syn::ForeignItem { }; BindgenAttrs::find(attrs)? }; - let js_namespace = item_opts.js_namespace().cloned(); + let js_namespace = item_opts.js_namespace().map(|(s, _)| s.to_owned()); let kind = match self { syn::ForeignItem::Fn(f) => f.convert((item_opts, &module))?, syn::ForeignItem::Type(t) => t.convert(item_opts)?, diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index caba298f1e0e..fdde3bc24466 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -22,7 +22,7 @@ macro_rules! shared_api { struct Import<'a> { module: ImportModule<'a>, - js_namespace: Option<&'a str>, + js_namespace: Option>, kind: ImportKind<'a>, } diff --git a/guide/src/reference/attributes/on-js-imports/js_namespace.md b/guide/src/reference/attributes/on-js-imports/js_namespace.md index 8873e8396d5d..0d6d6672548c 100644 --- a/guide/src/reference/attributes/on-js-imports/js_namespace.md +++ b/guide/src/reference/attributes/on-js-imports/js_namespace.md @@ -24,3 +24,18 @@ Foo::new(); This is an example of how to bind namespaced items in Rust. The `log` and `Foo::new` functions will be available in the Rust module and will be invoked as `console.log` and `new Bar.Foo` in JavaScript. + +It is also possible to access the JavaScript object under the nested namespace. +`js_namespace` also accepts the array of the string to specify the namespace. + +```rust +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = ["window", "document"])] + fn write(s: &str); +} + +write("hello, document!"); +``` + +This example shows how to bind `window.document.write` in Rust. diff --git a/tests/wasm/import_class.js b/tests/wasm/import_class.js index b86ab92be043..848be7b14f0a 100644 --- a/tests/wasm/import_class.js +++ b/tests/wasm/import_class.js @@ -140,3 +140,31 @@ exports.StaticStructural = class { return x + 3; } }; + +class InnerClass { + static inner_static_function(x) { + return x + 5; + } + + static create_inner_instance() { + const ret = new InnerClass(); + ret.internal_int = 3; + return ret; + } + + get_internal_int() { + return this.internal_int; + } + + append_to_internal_int(i) { + this.internal_int += i; + } + + assert_internal_int(i) { + assert.strictEqual(this.internal_int, i); + } +} + +exports.nestedNamespace = { + InnerClass: InnerClass +} diff --git a/tests/wasm/import_class.rs b/tests/wasm/import_class.rs index dcdbe745ec46..7de986147cbc 100644 --- a/tests/wasm/import_class.rs +++ b/tests/wasm/import_class.rs @@ -96,6 +96,19 @@ extern "C" { type StaticStructural; #[wasm_bindgen(static_method_of = StaticStructural, structural)] fn static_structural(a: u32) -> u32; + + #[derive(Clone)] + type InnerClass; + #[wasm_bindgen(js_namespace = ["nestedNamespace", "InnerClass"])] + fn inner_static_function(a: u32) -> u32; + #[wasm_bindgen(js_namespace = ["nestedNamespace", "InnerClass"])] + fn create_inner_instance() -> InnerClass; + #[wasm_bindgen(method)] + fn get_internal_int(this: &InnerClass) -> u32; + #[wasm_bindgen(method)] + fn append_to_internal_int(this: &InnerClass, i: u32); + #[wasm_bindgen(method)] + fn assert_internal_int(this: &InnerClass, i: u32); } #[wasm_bindgen] @@ -237,3 +250,14 @@ fn catch_constructors() { fn static_structural() { assert_eq!(StaticStructural::static_structural(30), 33); } + +#[wasm_bindgen_test] +fn nested_namespace() { + assert_eq!(InnerClass::inner_static_function(15), 20); + + let f = InnerClass::create_inner_instance(); + assert_eq!(f.get_internal_int(), 3); + assert_eq!(f.clone().get_internal_int(), 3); + f.append_to_internal_int(5); + f.assert_internal_int(8); +}