Skip to content

Commit 87663c6

Browse files
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 <[email protected]>
1 parent e0ad7bf commit 87663c6

File tree

11 files changed

+144
-24
lines changed

11 files changed

+144
-24
lines changed

crates/backend/src/ast.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ pub enum MethodSelf {
7575
#[derive(Clone)]
7676
pub struct Import {
7777
pub module: ImportModule,
78-
pub js_namespace: Option<Ident>,
78+
pub js_namespace: Option<Vec<String>>,
7979
pub kind: ImportKind,
8080
}
8181

crates/backend/src/codegen.rs

+17-14
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::util::ShortHash;
44
use crate::Diagnostic;
55
use proc_macro2::{Ident, Literal, Span, TokenStream};
66
use quote::{quote, ToTokens};
7-
use std::collections::HashSet;
7+
use std::collections::{HashMap, HashSet};
88
use std::sync::atomic::{AtomicUsize, Ordering};
99
use std::sync::Mutex;
1010
use syn;
@@ -32,28 +32,31 @@ impl TryToTokens for ast::Program {
3232
for s in self.structs.iter() {
3333
s.to_tokens(tokens);
3434
}
35-
let mut types = HashSet::new();
35+
let mut types = HashMap::new();
3636
for i in self.imports.iter() {
3737
if let ast::ImportKind::Type(t) = &i.kind {
38-
types.insert(t.rust_name.clone());
38+
types.insert(t.rust_name.to_string(), t.rust_name.clone());
3939
}
4040
}
4141
for i in self.imports.iter() {
4242
DescribeImport { kind: &i.kind }.to_tokens(tokens);
4343

4444
// If there is a js namespace, check that name isn't a type. If it is,
4545
// this import might be a method on that type.
46-
if let Some(ns) = &i.js_namespace {
47-
if types.contains(ns) && i.kind.fits_on_impl() {
48-
let kind = match i.kind.try_to_token_stream() {
49-
Ok(kind) => kind,
50-
Err(e) => {
51-
errors.push(e);
52-
continue;
53-
}
54-
};
55-
(quote! { impl #ns { #kind } }).to_tokens(tokens);
56-
continue;
46+
if let Some(nss) = &i.js_namespace {
47+
// When the namespace is `A.B`, the type name should be `B`.
48+
if let Some(ns) = nss.last().and_then(|t| types.get(t)) {
49+
if i.kind.fits_on_impl() {
50+
let kind = match i.kind.try_to_token_stream() {
51+
Ok(kind) => kind,
52+
Err(e) => {
53+
errors.push(e);
54+
continue;
55+
}
56+
};
57+
(quote! { impl #ns { #kind } }).to_tokens(tokens);
58+
continue;
59+
}
5760
}
5861
}
5962

crates/backend/src/encode.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ fn shared_import<'a>(i: &'a ast::Import, intern: &'a Interner) -> Result<Import<
241241
ast::ImportModule::Inline(idx, _) => ImportModule::Inline(*idx as u32),
242242
ast::ImportModule::None => ImportModule::None,
243243
},
244-
js_namespace: i.js_namespace.as_ref().map(|s| intern.intern(s)),
244+
js_namespace: i.js_namespace.clone(),
245245
kind: shared_import_kind(&i.kind, intern)?,
246246
})
247247
}

crates/cli-support/src/js/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1998,7 +1998,7 @@ impl<'a> Context<'a> {
19981998

19991999
JsImportName::VendorPrefixed { name, prefixes } => {
20002000
self.imports_post.push_str("const l");
2001-
self.imports_post.push_str(&name);
2001+
self.imports_post.push_str(name);
20022002
self.imports_post.push_str(" = ");
20032003
switch(&mut self.imports_post, name, "", prefixes);
20042004
self.imports_post.push_str(";\n");

crates/cli-support/src/wit/mod.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -898,7 +898,7 @@ impl<'a> Context<'a> {
898898
"import of `{}` through js namespace `{}` isn't supported \
899899
right now when it lists a polyfill",
900900
item,
901-
ns
901+
ns.join(".")
902902
);
903903
}
904904
return Ok(JsImport {
@@ -911,8 +911,12 @@ impl<'a> Context<'a> {
911911
}
912912

913913
let (name, fields) = match import.js_namespace {
914-
Some(ns) => (ns, vec![item.to_string()]),
915-
None => (item, Vec::new()),
914+
Some(ref ns) => {
915+
let mut tail = (&ns[1..]).to_owned();
916+
tail.push(item.to_string());
917+
(ns[0].to_owned(), tail)
918+
}
919+
None => (item.to_owned(), Vec::new()),
916920
};
917921

918922
let name = match import.module {

crates/macro-support/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ extra-traits = ["syn/extra-traits"]
1717
strict-macro = []
1818

1919
[dependencies]
20-
syn = { version = '1.0.27', features = ['visit'] }
20+
syn = { version = '1.0.27', features = ['visit', 'full'] }
2121
quote = '1.0'
2222
proc-macro2 = "1.0"
2323
wasm-bindgen-backend = { path = "../backend", version = "=0.2.63" }

crates/macro-support/src/parser.rs

+48-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use quote::ToTokens;
1111
use shared;
1212
use syn;
1313
use syn::parse::{Parse, ParseStream, Result as SynResult};
14+
use syn::spanned::Spanned;
1415

1516
thread_local!(static ATTRS: AttributeParseState = Default::default());
1617

@@ -34,7 +35,7 @@ macro_rules! attrgen {
3435
(constructor, Constructor(Span)),
3536
(method, Method(Span)),
3637
(static_method_of, StaticMethodOf(Span, Ident)),
37-
(js_namespace, JsNamespace(Span, Ident)),
38+
(js_namespace, JsNamespace(Span, Vec<String>, Vec<Span>)),
3839
(module, Module(Span, String, Span)),
3940
(raw_module, RawModule(Span, String, Span)),
4041
(inline_js, InlineJs(Span, String, Span)),
@@ -116,6 +117,21 @@ macro_rules! methods {
116117
}
117118
};
118119

120+
(@method $name:ident, $variant:ident(Span, Vec<String>, Vec<Span>)) => {
121+
fn $name(&self) -> Option<(&[String], &[Span])> {
122+
self.attrs
123+
.iter()
124+
.filter_map(|a| match &a.1 {
125+
BindgenAttr::$variant(_, ss, spans) => {
126+
a.0.set(true);
127+
Some((&ss[..], &spans[..]))
128+
}
129+
_ => None,
130+
})
131+
.next()
132+
}
133+
};
134+
119135
(@method $name:ident, $variant:ident(Span, $($other:tt)*)) => {
120136
#[allow(unused)]
121137
fn $name(&self) -> Option<&$($other)*> {
@@ -280,6 +296,36 @@ impl Parse for BindgenAttr {
280296
};
281297
return Ok(BindgenAttr::$variant(attr_span, val, span))
282298
});
299+
300+
(@parser $variant:ident(Span, Vec<String>, Vec<Span>)) => ({
301+
input.parse::<Token![=]>()?;
302+
let input_before_parse = input.fork();
303+
let (vals, spans) = match input.parse::<syn::ExprArray>() {
304+
Ok(exprs) => {
305+
let mut vals = vec![];
306+
let mut spans = vec![];
307+
308+
for expr in exprs.elems.iter() {
309+
if let syn::Expr::Lit(syn::ExprLit {
310+
lit: syn::Lit::Str(ref str),
311+
..
312+
}) = expr {
313+
vals.push(str.value());
314+
spans.push(str.span());
315+
} else {
316+
return Err(syn::Error::new(expr.span(), "expected string literals"));
317+
}
318+
}
319+
320+
(vals, spans)
321+
},
322+
Err(_) => {
323+
let ident = input_before_parse.parse::<AnyIdent>()?.0;
324+
(vec![ident.to_string()], vec![ident.span()])
325+
}
326+
};
327+
return Ok(BindgenAttr::$variant(attr_span, vals, spans))
328+
});
283329
}
284330

285331
attrgen!(parsers);
@@ -1270,7 +1316,7 @@ impl MacroParse<ast::ImportModule> for syn::ForeignItem {
12701316
};
12711317
BindgenAttrs::find(attrs)?
12721318
};
1273-
let js_namespace = item_opts.js_namespace().cloned();
1319+
let js_namespace = item_opts.js_namespace().map(|(s, _)| s.to_owned());
12741320
let kind = match self {
12751321
syn::ForeignItem::Fn(f) => f.convert((item_opts, &module))?,
12761322
syn::ForeignItem::Type(t) => t.convert(item_opts)?,

crates/shared/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ macro_rules! shared_api {
2222

2323
struct Import<'a> {
2424
module: ImportModule<'a>,
25-
js_namespace: Option<&'a str>,
25+
js_namespace: Option<Vec<String>>,
2626
kind: ImportKind<'a>,
2727
}
2828

guide/src/reference/attributes/on-js-imports/js_namespace.md

+15
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,18 @@ Foo::new();
2424
This is an example of how to bind namespaced items in Rust. The `log` and `Foo::new` functions will
2525
be available in the Rust module and will be invoked as `console.log` and `new Bar.Foo` in
2626
JavaScript.
27+
28+
It is also possible to access the JavaScript object under the nested namespace.
29+
`js_namespace` also accepts the array of the string to specify the namespace.
30+
31+
```rust
32+
#[wasm_bindgen]
33+
extern "C" {
34+
#[wasm_bindgen(js_namespace = ["window", "document"])]
35+
fn write(s: &str);
36+
}
37+
38+
write("hello, document!");
39+
```
40+
41+
This example shows how to bind `window.document.write` in Rust.

tests/wasm/import_class.js

+28
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,31 @@ exports.StaticStructural = class {
140140
return x + 3;
141141
}
142142
};
143+
144+
class InnerClass {
145+
static inner_static_function(x) {
146+
return x + 5;
147+
}
148+
149+
static create_inner_instance() {
150+
const ret = new InnerClass();
151+
ret.internal_int = 3;
152+
return ret;
153+
}
154+
155+
get_internal_int() {
156+
return this.internal_int;
157+
}
158+
159+
append_to_internal_int(i) {
160+
this.internal_int += i;
161+
}
162+
163+
assert_internal_int(i) {
164+
assert.strictEqual(this.internal_int, i);
165+
}
166+
}
167+
168+
exports.nestedNamespace = {
169+
InnerClass: InnerClass
170+
}

tests/wasm/import_class.rs

+24
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,19 @@ extern "C" {
9696
type StaticStructural;
9797
#[wasm_bindgen(static_method_of = StaticStructural, structural)]
9898
fn static_structural(a: u32) -> u32;
99+
100+
#[derive(Clone)]
101+
type InnerClass;
102+
#[wasm_bindgen(js_namespace = ["nestedNamespace", "InnerClass"])]
103+
fn inner_static_function(a: u32) -> u32;
104+
#[wasm_bindgen(js_namespace = ["nestedNamespace", "InnerClass"])]
105+
fn create_inner_instance() -> InnerClass;
106+
#[wasm_bindgen(method)]
107+
fn get_internal_int(this: &InnerClass) -> u32;
108+
#[wasm_bindgen(method)]
109+
fn append_to_internal_int(this: &InnerClass, i: u32);
110+
#[wasm_bindgen(method)]
111+
fn assert_internal_int(this: &InnerClass, i: u32);
99112
}
100113

101114
#[wasm_bindgen]
@@ -237,3 +250,14 @@ fn catch_constructors() {
237250
fn static_structural() {
238251
assert_eq!(StaticStructural::static_structural(30), 33);
239252
}
253+
254+
#[wasm_bindgen_test]
255+
fn nested_namespace() {
256+
assert_eq!(InnerClass::inner_static_function(15), 20);
257+
258+
let f = InnerClass::create_inner_instance();
259+
assert_eq!(f.get_internal_int(), 3);
260+
assert_eq!(f.clone().get_internal_int(), 3);
261+
f.append_to_internal_int(5);
262+
f.assert_internal_int(8);
263+
}

0 commit comments

Comments
 (0)