mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-03-15 17:50:51 +00:00
* 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:
parent
e0ad7bfeac
commit
87663c6d2a
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
|
@ -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 {
|
||||||
|
@ -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" }
|
||||||
|
@ -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)?,
|
||||||
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user