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 <alex@alexcrichton.com>
This commit is contained in:
Hajime Fukuda 2020-05-27 08:36:20 -07:00 committed by GitHub
parent e0ad7bfeac
commit 87663c6d2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 144 additions and 24 deletions

View File

@ -75,7 +75,7 @@ pub enum MethodSelf {
#[derive(Clone)] #[derive(Clone)]
pub struct Import { pub struct Import {
pub module: ImportModule, pub module: ImportModule,
pub js_namespace: Option<Ident>, pub js_namespace: Option<Vec<String>>,
pub kind: ImportKind, pub kind: ImportKind,
} }

View File

@ -4,7 +4,7 @@ use crate::util::ShortHash;
use crate::Diagnostic; use crate::Diagnostic;
use proc_macro2::{Ident, Literal, Span, TokenStream}; use proc_macro2::{Ident, Literal, Span, TokenStream};
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
use std::collections::HashSet; use std::collections::{HashMap, HashSet};
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex; use std::sync::Mutex;
use syn; use syn;
@ -32,10 +32,10 @@ impl TryToTokens for ast::Program {
for s in self.structs.iter() { for s in self.structs.iter() {
s.to_tokens(tokens); s.to_tokens(tokens);
} }
let mut types = HashSet::new(); let mut types = HashMap::new();
for i in self.imports.iter() { for i in self.imports.iter() {
if let ast::ImportKind::Type(t) = &i.kind { 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() { 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, // 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. // this import might be a method on that type.
if let Some(ns) = &i.js_namespace { if let Some(nss) = &i.js_namespace {
if types.contains(ns) && i.kind.fits_on_impl() { // When the namespace is `A.B`, the type name should be `B`.
let kind = match i.kind.try_to_token_stream() { if let Some(ns) = nss.last().and_then(|t| types.get(t)) {
Ok(kind) => kind, if i.kind.fits_on_impl() {
Err(e) => { let kind = match i.kind.try_to_token_stream() {
errors.push(e); Ok(kind) => kind,
continue; Err(e) => {
} errors.push(e);
}; continue;
(quote! { impl #ns { #kind } }).to_tokens(tokens); }
continue; };
(quote! { impl #ns { #kind } }).to_tokens(tokens);
continue;
}
} }
} }

View File

@ -241,7 +241,7 @@ fn shared_import<'a>(i: &'a ast::Import, intern: &'a Interner) -> Result<Import<
ast::ImportModule::Inline(idx, _) => ImportModule::Inline(*idx as u32), ast::ImportModule::Inline(idx, _) => ImportModule::Inline(*idx as u32),
ast::ImportModule::None => ImportModule::None, 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)?, kind: shared_import_kind(&i.kind, intern)?,
}) })
} }

View File

@ -1998,7 +1998,7 @@ impl<'a> Context<'a> {
JsImportName::VendorPrefixed { name, prefixes } => { JsImportName::VendorPrefixed { name, prefixes } => {
self.imports_post.push_str("const l"); self.imports_post.push_str("const l");
self.imports_post.push_str(&name); self.imports_post.push_str(name);
self.imports_post.push_str(" = "); self.imports_post.push_str(" = ");
switch(&mut self.imports_post, name, "", prefixes); switch(&mut self.imports_post, name, "", prefixes);
self.imports_post.push_str(";\n"); self.imports_post.push_str(";\n");

View File

@ -898,7 +898,7 @@ impl<'a> Context<'a> {
"import of `{}` through js namespace `{}` isn't supported \ "import of `{}` through js namespace `{}` isn't supported \
right now when it lists a polyfill", right now when it lists a polyfill",
item, item,
ns ns.join(".")
); );
} }
return Ok(JsImport { return Ok(JsImport {
@ -911,8 +911,12 @@ impl<'a> Context<'a> {
} }
let (name, fields) = match import.js_namespace { let (name, fields) = match import.js_namespace {
Some(ns) => (ns, vec![item.to_string()]), Some(ref ns) => {
None => (item, Vec::new()), 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 { let name = match import.module {

View File

@ -17,7 +17,7 @@ extra-traits = ["syn/extra-traits"]
strict-macro = [] strict-macro = []
[dependencies] [dependencies]
syn = { version = '1.0.27', features = ['visit'] } syn = { version = '1.0.27', features = ['visit', 'full'] }
quote = '1.0' quote = '1.0'
proc-macro2 = "1.0" proc-macro2 = "1.0"
wasm-bindgen-backend = { path = "../backend", version = "=0.2.63" } wasm-bindgen-backend = { path = "../backend", version = "=0.2.63" }

View File

@ -11,6 +11,7 @@ use quote::ToTokens;
use shared; use shared;
use syn; use syn;
use syn::parse::{Parse, ParseStream, Result as SynResult}; use syn::parse::{Parse, ParseStream, Result as SynResult};
use syn::spanned::Spanned;
thread_local!(static ATTRS: AttributeParseState = Default::default()); thread_local!(static ATTRS: AttributeParseState = Default::default());
@ -34,7 +35,7 @@ macro_rules! attrgen {
(constructor, Constructor(Span)), (constructor, Constructor(Span)),
(method, Method(Span)), (method, Method(Span)),
(static_method_of, StaticMethodOf(Span, Ident)), (static_method_of, StaticMethodOf(Span, Ident)),
(js_namespace, JsNamespace(Span, Ident)), (js_namespace, JsNamespace(Span, Vec<String>, Vec<Span>)),
(module, Module(Span, String, Span)), (module, Module(Span, String, Span)),
(raw_module, RawModule(Span, String, Span)), (raw_module, RawModule(Span, String, Span)),
(inline_js, InlineJs(Span, String, Span)), (inline_js, InlineJs(Span, String, Span)),
@ -116,6 +117,21 @@ macro_rules! methods {
} }
}; };
(@method $name:ident, $variant:ident(Span, Vec<String>, Vec<Span>)) => {
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)*)) => { (@method $name:ident, $variant:ident(Span, $($other:tt)*)) => {
#[allow(unused)] #[allow(unused)]
fn $name(&self) -> Option<&$($other)*> { fn $name(&self) -> Option<&$($other)*> {
@ -280,6 +296,36 @@ impl Parse for BindgenAttr {
}; };
return Ok(BindgenAttr::$variant(attr_span, val, span)) return Ok(BindgenAttr::$variant(attr_span, val, span))
}); });
(@parser $variant:ident(Span, Vec<String>, Vec<Span>)) => ({
input.parse::<Token![=]>()?;
let input_before_parse = input.fork();
let (vals, spans) = match input.parse::<syn::ExprArray>() {
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::<AnyIdent>()?.0;
(vec![ident.to_string()], vec![ident.span()])
}
};
return Ok(BindgenAttr::$variant(attr_span, vals, spans))
});
} }
attrgen!(parsers); attrgen!(parsers);
@ -1270,7 +1316,7 @@ impl MacroParse<ast::ImportModule> for syn::ForeignItem {
}; };
BindgenAttrs::find(attrs)? 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 { let kind = match self {
syn::ForeignItem::Fn(f) => f.convert((item_opts, &module))?, syn::ForeignItem::Fn(f) => f.convert((item_opts, &module))?,
syn::ForeignItem::Type(t) => t.convert(item_opts)?, syn::ForeignItem::Type(t) => t.convert(item_opts)?,

View File

@ -22,7 +22,7 @@ macro_rules! shared_api {
struct Import<'a> { struct Import<'a> {
module: ImportModule<'a>, module: ImportModule<'a>,
js_namespace: Option<&'a str>, js_namespace: Option<Vec<String>>,
kind: ImportKind<'a>, kind: ImportKind<'a>,
} }

View File

@ -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 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 be available in the Rust module and will be invoked as `console.log` and `new Bar.Foo` in
JavaScript. 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.

View File

@ -140,3 +140,31 @@ exports.StaticStructural = class {
return x + 3; 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
}

View File

@ -96,6 +96,19 @@ extern "C" {
type StaticStructural; type StaticStructural;
#[wasm_bindgen(static_method_of = StaticStructural, structural)] #[wasm_bindgen(static_method_of = StaticStructural, structural)]
fn static_structural(a: u32) -> u32; 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] #[wasm_bindgen]
@ -237,3 +250,14 @@ fn catch_constructors() {
fn static_structural() { fn static_structural() {
assert_eq!(StaticStructural::static_structural(30), 33); 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);
}