Migrate from a macro to an attribute

This commit migrates from `wasm_bindgen!`-the-macro to
`#[wasm_bindgen]`-the-attribute. The actual mechanics of the macro are
relatively simple in just generating some shims here and there, but wrapping
everything in one huge macro invocation can often seem intimidating as it gives
off this feeling of "oh dear anything can happen here!" Using an attribute
should curb expectations much more greatly of "oh there's just some extra stuff
happening behind the scenes".

The usage is otherwise relatively straightforward and close to what it was
before, but check out the DESIGN.md/README.md changes for more info!
This commit is contained in:
Alex Crichton 2018-02-07 16:41:33 -08:00
parent 70614f808e
commit 29771b574c
15 changed files with 1400 additions and 1273 deletions

209
DESIGN.md
View File

@ -28,6 +28,41 @@ desired interface expressed in JS (classes, types, strings, etc) and the
`foo_wasm.wasm` module is simply used as an implementation detail (it was
lightly modified from the original `foo.wasm` file).
## Foundation #2: Unintrusive in Rust
On the more Rust-y side of things the `wasm-bindgen` crate is designed to
ideally have as minimal impact on a Rust crate as possible. Ideally a few
`#[wasm_bindgen]` attributes are annotated in key locations and otherwise you're
off to the races, but otherwise it strives to both not invent new syntax and
work with existing idioms today.
For example the `#[no_mangle]` and `extern` ABI indicators are required for
annotated free functions with `#[wasm_bindgen]`, because these two snippets are
actually equivalent:
```rust
#[no_mangle]
pub extern fn only_integers(a: i32) -> u32 {
// ...
}
// is equivalent to...
#[wasm_bindgen]
#[no_mangle]
pub extern fn only_integers_with_wasm_bindgen(a: i32) -> u32 {
// ...
}
```
Additionally the design here with minimal intervention in Rust should allow us
to easily take advantage of the upcoming [host bindings][host] proposal. Ideally
you'd simply upgrade `wasm-bindgen`-the-crate as well as you're toolchain and
you're immediately getting raw access to host bindigns! (this is still a bit of
aways off though...)
[host]: https://github.com/WebAssembly/host-bindings
## Polyfill for "JS objects in wasm"
One of the main goals of `wasm-bindgen` is to allow working with and passing
@ -52,10 +87,10 @@ Let's take a look at an example.
```rust
// foo.rs
wasm_bindgen! {
pub fn foo(a: &JsValue) {
// ...
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn foo(a: &JsValue) {
// ...
}
```
@ -108,11 +143,11 @@ Here we can see a few notable points of action:
used, issuing a `pop` for what was pushed at the start of the function.
It's also helpful to dig into the Rust side of things to see what's going on
there! Let's take a look at the code that `wasm_bindgen!` generates in Rust:
there! Let's take a look at the code that `#[wasm_bindgen]` generates in Rust:
```rust
// what the user wrote
pub fn foo(a: &JsValue) {
// what the user wrote, note that #[no_mangle] is removed
pub extern fn foo(a: &JsValue) {
// ...
}
@ -152,10 +187,9 @@ example.
```rust
// foo.rs
wasm_bindgen! {
pub fn foo(a: JsValue) {
// ...
}
#[wasm_bindgen]
pub fn foo(a: JsValue) {
// ...
}
```
@ -216,7 +250,7 @@ And finally, let's take a look at the Rust generated again too:
```rust
// what the user wrote
pub fn foo(a: JsValue) {
pub extern fn foo(a: JsValue) {
// ...
}
@ -283,10 +317,10 @@ The most interesting conversion here happens with strings so let's take a look
at that.
```rust
wasm_bindgen! {
pub fn greet(a: &str) -> String {
format!("Hello, {}!", a)
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn greet(a: &str) -> String {
format!("Hello, {}!", a)
}
```
@ -358,7 +392,7 @@ At this point it may be predictable, but let's take a look at the Rust side of
things as well
```rust
pub fn greet(a: &str) -> String {
pub extern fn greet(a: &str) -> String {
format!("Hello, {}!", a)
}
@ -384,7 +418,7 @@ then returned up to was for reading via the `__wbindgen_boxed_str_*` functions.
So in general exporting a function involves a shim both in JS and in Rust with
each side translating to or from wasm arguments to the native types of each
language. The `wasm-bindgen` tool manages hooking up all these shims while the
`wasm_bindgen!` macro takes care of the Rust shim as well.
`#[wasm_bindgen]` macro takes care of the Rust shim as well.
Most arguments have a relatively clear way to convert them, bit if you've got
any questions just let me know!
@ -399,11 +433,9 @@ First up, let's say we invert the function above and instead want to generate
greetings in JS but call it from Rust. We might have, for example:
```rust
wasm_bindgen! {
#[wasm_module = "./greet"]
extern "JS" {
fn greet(a: &str) -> String;
}
#[wasm_bindgen(module = "./greet")]
extern {
fn greet(a: &str) -> String;
}
fn other_code() {
@ -412,8 +444,9 @@ fn other_code() {
}
```
The basic idea of exports is the same in that we'll have shims in both JS and
Rust doing the necessary translation. Let's first see the JS shim in action:
The basic idea of imports is the same as exports in that we'll have shims in
both JS and Rust doing the necessary translation. Let's first see the JS shim in
action:
```js
import * as wasm from './foo_wasm';
@ -442,7 +475,7 @@ There's some tricky ABI business going on here so let's take a look at the
generated Rust as well:
```rust
fn greet(a: &str) -> String {
extern fn greet(a: &str) -> String {
extern {
fn __wbg_f_greet(a_ptr: *const u8, a_len: usize, ret_len: *mut usize) -> *mut u8;
}
@ -473,35 +506,37 @@ sometimes, though, want to go even further and define a JS `class` in Rust. Or
in other words, we want to expose an object with methods from Rust to JS rather
than just importing/exporting free functions.
The `wasm_bindgen!` macro currently allows an `impl` block in Rust which does
just this:
The `#[wasm_bindgen]` attribute can annotate both a `struct` and `impl` blocks
to allow:
```rust
wasm_bindgen! {
pub struct Foo {
internal: i32,
#[wasm_bindgen]
pub struct Foo {
internal: i32,
}
#[wasm_bindgen]
impl Foo {
pub fn new(val: i32) -> Foo {
Foo { internal: val }
}
impl Foo {
pub fn new(val: i32) -> Foo {
Foo { internal: val }
}
pub fn get(&self) -> i32 {
self.internal
}
pub fn get(&self) -> i32 {
self.internal
}
pub fn set(&mut self, val: i32) {
self.internal = val;
}
pub fn set(&mut self, val: i32) {
self.internal = val;
}
}
```
This is a typical Rust `struct` definition for a type with a constructor and a
few methods. By encasing it in the `wasm_bindgen!` macro we're ensuring that the
types and methods are also available to JS. If we take a look at the generated
JS code for this we'll see:
few methods. Annotating the struct with `#[wasm_bindgen]` means that we'll
generate necessary trait impls to convert this type to/from the JS boundary. The
annotated `impl` block here means that the functions inside will also be made
available to JS through generated shims. If we take a look at the generated JS
code for this we'll see:
```js
import * as wasm from './foo_wasm';
@ -553,7 +588,7 @@ The real trickery with these bindings ends up happening in Rust, however, so
let's take a look at that.
```rust
// original input to `wasm_bindgen!` omitted ...
// original input to `#[wasm_bindgen]` omitted ...
#[export_name = "foo_new"]
pub extern fn __wasm_bindgen_generated_Foo_new(arg0: i32) -> u32
@ -597,14 +632,14 @@ same as [`RefCell`] and can be mostly glossed over.
The purpose for this type, if you're interested though, is to uphold Rust's
guarantees about aliasing in a world where aliasing is rampant (JS).
Specifically the `&Foo` type means that there can be as much alaising as you'd
like, but crucially `&mut Foo` means that it is the sole pointer to the data (no
other `&Foo` to the same instance exists). The [`RefCell`] type in libstd is a
way of dynamically enforcing this at runtime (as opposed to compile time where
it usually happens). Baking in `WasmRefCell` is the same idea here, adding
runtime checks for aliasing which are typically happening at compile time. This
is currently a Rust-specific feature which isn't actually in the `wasm-bindgen`
tool itself, it's just in the Rust-generated code (aka the `wasm_bindgen!`
macro).
like, but crucially `&mut Foo` means that it is the sole pointer to the data
(no other `&Foo` to the same instance exists). The [`RefCell`] type in libstd
is a way of dynamically enforcing this at runtime (as opposed to compile time
where it usually happens). Baking in `WasmRefCell` is the same idea here,
adding runtime checks for aliasing which are typically happening at compile
time. This is currently a Rust-specific feature which isn't actually in the
`wasm-bindgen` tool itself, it's just in the Rust-generated code (aka the
`#[wasm_bindgen]` attribute).
[`RefCell`]: https://doc.rust-lang.org/std/cell/struct.RefCell.html
@ -619,15 +654,21 @@ object bindings describe above.
As usual though, let's dive into an example!
```rust
wasm_bindgen! {
#[wasm_module = "./bar"]
extern struct Bar {
#[wasm_bindgen(constructor)]
fn new(arg: i32) -> Bar;
fn another_function() -> i32;
fn get(&self) -> i32;
fn set(&self, val: i32);
}
#[wasm_bindgen(module = "./bar")]
extern {
type Bar;
#[wasm_bindgen(constructor)]
fn new(arg: i32) -> Bar;
#[wasm_bindgen(static = Bar)]
fn another_function() -> i32;
#[wasm_bindgen(method)]
fn get(this: &Bar) -> i32;
#[wasm_bindgen(method)]
fn set(this: &Bar, val: i32);
}
fn run() {
@ -637,8 +678,28 @@ fn run() {
}
```
Here we're going to do the opposite of the above example and instead import our
class and use it from Rust. First up, let's look at the JS:
Unlike our previous imports, this one's a bit more chatty! Remember that one of
the goals of `wasm-bindgen` is to use native Rust syntax wherever possible, so
this is mostly intended to use the `#[wasm_bindgen]` attribute to interpret
what's written down in Rust. Now there's a few attribute annotations here, so
let's go through one-by-one:
* `#[wasm_bindgen(module = "./bar")]` - seen before with imports this is declare
where all the subsequent functionality is imported form. For example the `Bar`
type is going to be imported from the `./bar` module.
* `type Bar` - this is a declaration of JS class as a new type in Rust. This
means that a new type `Bar` is generated which is "opaque" but is represented
as internally containing a `JsValue`. We'll see more on this later.
* `#[wasm_bindgen(constructor)]` - this indicates that the binding's name isn't
actually used in JS but rather translates to `new Bar()`. The return value of
this function must be a bare type, like `Bar`.
* `#[wasm_bindgen(static = Bar)]` - this attribute indicates that the function
declaration is a static function accessed through the `Bar` class in JS.
* `#[wasm_bindgen(method)]` - and finally, this attribute indicates that a
method call is going to happen. The first argument must be a JS struct, like
`Bar`, and the call in JS looks like `Bar.prototype.set.call(...)`.
With all tha tin mind, let's take a look at the JS generated.
```js
import * as wasm from './foo_wasm';
@ -724,6 +785,14 @@ impl Bar {
}
}
}
impl WasmBoundary for Bar {
// ...
}
impl ToRefWasmBoundary for Bar {
// ...
}
```
In Rust we're seeing that a new type, `Bar`, is generated for this import of a
@ -748,12 +817,10 @@ In the meantime though fear not! You can, if necessary, annotate some imports
as whether they should catch an exception. For example:
```rust
wasm_bindgen! {
#[wasm_module = "./bar"]
extern "JS" {
#[wasm_bindgen(catch)]
fn foo() -> Result<(), JsValue>;
}
#[wasm_bindgen(module = "./bar")]
extern {
#[wasm_bindgen(catch)]
fn foo() -> Result<(), JsValue>;
}
```

195
README.md
View File

@ -77,16 +77,22 @@ extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
wasm_bindgen! {
pub fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
#[wasm_bindgen]
extern {
fn alert(s: &str);
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn greet(name: &str) {
alert(&format!("Hello, {}!", name));
}
```
Here we're wrapping the code we'd like to export to JS in the `wasm_bindgen!`
macro. We'll see more features later, but it suffices to say that most Rust
syntax fits inside here, it's not too special beyond what it generates!
And that's it! If we were to write the `greet` function naively without the
`#[wasm_bindgen]` attribute then JS wouldn't be able to communicate with the
types like `str`, so slapping a `#[wasm_bindgen]` on the function and the import
of `alert` ensures that the right shims are generated.
Next up let's build our project:
@ -155,7 +161,7 @@ import { greet } from "./js_hello_world";
import { booted } from "./js_hello_world_wasm";
booted.then(() => {
alert(greet("World!"))
greet("World!");
});
```
@ -188,31 +194,31 @@ If you open that in a browser you should see a `Hello, world!` dialog pop up!
## What just happened?
Phew! That was a lot of words and a lot ended up happening along the way. There
were two main pieces of magic happening: the `wasm_bindgen!` macro and the
were two main pieces of magic happening: the `#[wasm_bindgen]` attribute and the
`wasm-bindgen` CLI tool.
**The `wasm_bindgen!` macro**
**The `#[wasm_bindgen]` attribute**
This macro, exported from the `wasm-bindgen` crate, is the entrypoint to
This attribute, exported from the `wasm-bindgen` crate, is the entrypoint to
exposing Rust functions to JS. This is a procedural macro (hence requiring the
nightly Rust toolchain) which will transform the definitions inside and prepare
appropriate wrappers to receive JS-compatible types and convert them to
Rust-compatible types.
nightly Rust toolchain) which will generate the appropriate shims in Rust to
translate from your type signature to one that JS can interface with. Finally
the attribute also serializes some information to the output artifact which
`wasm-bindgen`-the-tool will discard after it parses.
There's a more thorough explanation below of the various bits and pieces of the
macro, but it suffices for now to say that you can have free functions, structs,
and impl blocks for those structs in the macro right now. Many Rust features
aren't supported in these blocks like generics, lifetime parameters, etc.
Additionally not all types can be taken or returned from the functions. In
general though simple-ish types should work just fine!
attribute, but it suffices for now to say that you can attach it to free
functions, structs, impl blocks for those structs and `extern { ... }` blocks.
Some Rust features like generics, lifetime parameters, etc, aren't supported on
functions tagged with `#[wasm_bindgen]` right now.
**The `wasm-bindgen` CLI tool**
The next half of what happened here was all in the `wasm-bindgen` tool. This
tool opened up the wasm module that rustc generated and found an encoded
description of what was passed to the `wasm_bindgen!` macro. You can think of
this as the `wasm_bindgen!` macro created a special section of the output module
which `wasm-bindgen` strips and processes.
description of what was passed to the `#[wasm_bindgen]` attribute. You can
think of this as the `#[wasm_bindgen]` attribute created a special section of
the output module which `wasm-bindgen` strips and processes.
This information gave `wasm-bindgen` all it needed to know to generate the JS
file that we then imported. The JS file wraps instantiating the underlying wasm
@ -231,62 +237,76 @@ extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
wasm_bindgen! {
// Strings can both be passed in and received
pub fn concat(a: &str, b: &str) -> String {
let mut a = a.to_string();
a.push_str(b);
return a
// Strings can both be passed in and received
#[wasm_bindgen]
#[no_mangle]
pub extern fn concat(a: &str, b: &str) -> String {
let mut a = a.to_string();
a.push_str(b);
return a
}
// A struct will show up as a class on the JS side of things
#[wasm_bindgen]
pub struct Foo {
contents: u32,
}
#[wasm_bindgen]
impl Foo {
pub fn new() -> Foo {
Foo { contents: 0 }
}
// A struct will show up as a class on the JS side of things
pub struct Foo {
contents: u32,
// Methods can be defined with `&mut self` or `&self`, and arguments you
// can pass to a normal free function also all work in methods.
pub fn add(&mut self, amt: u32) -> u32 {
self.contents += amt;
return self.contents
}
impl Foo {
pub fn new() -> Foo {
Foo { contents: 0 }
}
// Methods can be defined with `&mut self` or `&self`, and arguments you
// can pass to a normal free function also all work in methods.
pub fn add(&mut self, amt: u32) -> u32 {
self.contents += amt;
return self.contents
}
// You can also take a limited set of references to other types as well.
pub fn add_other(&mut self, bar: &Bar) {
self.contents += bar.contents;
}
// Ownership can work too!
pub fn consume_other(&mut self, bar: Bar) {
self.contents += bar.contents;
}
// You can also take a limited set of references to other types as well.
pub fn add_other(&mut self, bar: &Bar) {
self.contents += bar.contents;
}
pub struct Bar {
contents: u32,
opaque: JsValue, // defined in `wasm_bindgen`, imported via prelude
// Ownership can work too!
pub fn consume_other(&mut self, bar: Bar) {
self.contents += bar.contents;
}
}
#[wasm_bindgen]
pub struct Bar {
contents: u32,
opaque: JsValue, // defined in `wasm_bindgen`, imported via prelude
}
#[wasm_bindgen(module = "./index")] // what ES6 module to import from
extern {
fn bar_on_reset(to: &str, opaque: &JsValue);
// We can import classes and annotate functionality on those classes as well
type Awesome;
#[wasm_bindgen(constructor)]
fn new() -> Awesome;
#[wasm_bindgen(method)]
fn get_internal(this: &Awesome) -> u32;
}
#[wasm_bindgen]
impl Bar {
pub fn from_str(s: &str, opaque: JsValue) -> Bar {
let contents = s.parse().unwrap_or_else(|| {
Awesome::new().get_internal()
});
Bar { contents, opaque }
}
#[wasm_module = "./index"] // what ES6 module to import this functionality from
extern "JS" {
fn bar_on_reset(to: &str, opaque: &JsValue);
}
impl Bar {
pub fn from_str(s: &str, opaque: JsValue) -> Bar {
Bar { contents: s.parse().unwrap_or(0), opaque }
}
pub fn reset(&mut self, s: &str) {
if let Ok(n) = s.parse() {
bar_on_reset(s, &self.opaque);
self.contents = n;
}
pub fn reset(&mut self, s: &str) {
if let Ok(n) = s.parse() {
bar_on_reset(s, &self.opaque);
self.contents = n;
}
}
}
@ -295,7 +315,7 @@ wasm_bindgen! {
The generated JS bindings for this invocation of the macro [look like
this][bindings]. You can view them in action like so:
[bindings]: https://gist.github.com/alexcrichton/12ccab3a18d7db0e0d7d777a0f4951b5
[bindings]: https://gist.github.com/alexcrichton/3d85c505e785fb8ff32e2c1cf9618367
and our corresponding `index.js`:
@ -341,6 +361,16 @@ function main() {
alert('all passed!')
}
export class Awesome {
constructor() {
this.internal = 32;
}
get_internal() {
return this.internal;
}
}
booted.then(main);
```
@ -352,21 +382,25 @@ should also be a great place to look for examples.
[tests]: https://github.com/alexcrichton/wasm-bindgen/tree/master/tests
In the `wasm_bindgen!` macro you can have four items: functions, structs,
impls, and foreign modules. Impls can only contain functions. No lifetime
parameters or type parameters are allowed on any of these types. Foreign
modules must have the `"JS"` abi and currently only allow integer/string
arguments and integer return values.
The `#[wasm_bindgen]` attribute can be attached to functions, structs,
impls, and foreign modules. Impls can only contain functions, and the attribute
cannot be attached to functions in an impl block or functions in a foreign
module. No lifetime parameters or type parameters are allowed on any of these
types. Foreign modules must have the `"C"` abi (or none listed). Free functions
with `#[wasm_bindgen]` must also have the `"C"` abi or none listed and also be
annotated with the `#[no_mangle]` attribute.
All structs referenced through arguments to functions should be defined in the
macro itself. Arguments allowed are:
macro itself. Arguments allowed implement the `WasmBoundary` trait, and examples
are:
* Integers (not u64/i64)
* Floats
* Borrowed strings (`&str`)
* Owned strings (`String`)
* Owned structs (`Foo`) defined in the same bindgen macro
* Borrowed structs (`&Foo` or `&mut Bar`) defined in the same bindgen macro
* Exported structs (`Foo`, annotated with `#[wasm_bindgen]`)
* Imported types in a foreign module annotated with `#[wasm_bindgen]`
* Borrowed exported structs (`&Foo` or `&mut Bar`)
* The `JsValue` type and `&JsValue` (not mutable references)
All of the above can also be returned except borrowed references. Strings are
@ -382,7 +416,8 @@ safety with reentrancy and aliasing in JS. In general you shouldn't see
JS-values-in-Rust are implemented through indexes that index a table generated
as part of the JS bindings. This table is managed via the ownership specified in
Rust and through the bindings that we're returning.
Rust and through the bindings that we're returning. More information about this
can be found in the [design doc].
All of these constructs currently create relatively straightforward code on the
JS side of things, mostly having a 1:1 match in Rust with JS.

View File

@ -1,5 +1,6 @@
use std::char;
use std::collections::{HashSet, HashMap};
use std::mem;
use shared;
use parity_wasm::elements::*;
@ -16,6 +17,14 @@ pub struct Context<'a> {
pub module: &'a mut Module,
pub imports_to_rewrite: HashSet<String>,
pub custom_type_names: HashMap<char, String>,
pub imported_names: HashSet<String>,
pub exported_classes: HashMap<String, ExportedClass>,
}
#[derive(Default)]
pub struct ExportedClass {
pub contents: String,
pub typescript: String,
}
pub struct SubContext<'a, 'b: 'a> {
@ -37,6 +46,7 @@ impl<'a> Context<'a> {
}
pub fn finalize(&mut self, module_name: &str) -> (String, String) {
self.write_classes();
{
let mut bind = |name: &str, f: &Fn(&mut Self) -> String| {
if !self.wasm_import_needed(name) {
@ -202,6 +212,52 @@ impl<'a> Context<'a> {
(js, self.typescript.clone())
}
fn write_classes(&mut self) {
let classes = mem::replace(&mut self.exported_classes, Default::default());
for (class, exports) in classes {
let mut dst = String::new();
dst.push_str(&format!("export class {} {{", class));
let mut ts_dst = dst.clone();
ts_dst.push_str("
public ptr: number;
");
if self.config.debug {
self.expose_check_token();
dst.push_str(&format!("
constructor(ptr, sym) {{
_checkToken(sym);
this.ptr = ptr;
}}
"));
ts_dst.push_str("constructor(ptr: number, sym: Symbol);\n");
} else {
dst.push_str(&format!("
constructor(ptr) {{
this.ptr = ptr;
}}
"));
ts_dst.push_str("constructor(ptr: number);\n");
}
dst.push_str(&format!("
free() {{
const ptr = this.ptr;
this.ptr = 0;
wasm.{}(ptr);
}}
", shared::free_function(&class)));
ts_dst.push_str("free(): void;\n");
dst.push_str(&exports.contents);
ts_dst.push_str(&exports.contents);
dst.push_str("}\n");
ts_dst.push_str("}\n");
self.globals.push_str(&dst);
self.typescript.push_str(&ts_dst);
}
}
fn rewrite_imports(&mut self, module_name: &str) {
for section in self.module.sections_mut() {
let imports = match *section {
@ -584,27 +640,22 @@ impl<'a> Context<'a> {
impl<'a, 'b> SubContext<'a, 'b> {
pub fn generate(&mut self) {
for f in self.program.free_functions.iter() {
self.generate_free_function(f);
for f in self.program.exports.iter() {
self.generate_export(f);
}
for f in self.program.imports.iter() {
self.generate_import(f);
}
for s in self.program.structs.iter() {
self.generate_struct(s);
}
for s in self.program.imported_structs.iter() {
self.generate_import_struct(s);
}
}
pub fn generate_free_function(&mut self, func: &shared::Function) {
pub fn generate_export(&mut self, export: &shared::Export) {
if let Some(ref class) = export.class {
return self.generate_export_for_class(class, export)
}
let (js, ts) = self.generate_function("function",
&func.name,
&func.name,
&export.function.name,
false,
&func.arguments,
func.ret.as_ref());
&export.function);
self.cx.globals.push_str("export ");
self.cx.globals.push_str(&js);
self.cx.globals.push_str("\n");
@ -613,86 +664,37 @@ impl<'a, 'b> SubContext<'a, 'b> {
self.cx.typescript.push_str("\n");
}
pub fn generate_struct(&mut self, s: &shared::Struct) {
let mut dst = String::new();
dst.push_str(&format!("export class {} {{", s.name));
let mut ts_dst = dst.clone();
ts_dst.push_str("
public ptr: number;
");
if self.cx.config.debug {
self.cx.expose_check_token();
dst.push_str(&format!("
constructor(ptr, sym) {{
_checkToken(sym);
this.ptr = ptr;
}}
"));
ts_dst.push_str("constructor(ptr: number, sym: Symbol);\n");
} else {
dst.push_str(&format!("
constructor(ptr) {{
this.ptr = ptr;
}}
"));
ts_dst.push_str("constructor(ptr: number);\n");
}
dst.push_str(&format!("
free() {{
const ptr = this.ptr;
this.ptr = 0;
wasm.{}(ptr);
}}
", shared::free_function(&s.name)));
ts_dst.push_str("free(): void;\n");
for function in s.functions.iter() {
let (js, ts) = self.generate_function(
"static",
&function.name,
&shared::struct_function_export_name(&s.name, &function.name),
false,
&function.arguments,
function.ret.as_ref(),
);
dst.push_str(&js);
dst.push_str("\n");
ts_dst.push_str(&ts);
ts_dst.push_str("\n");
}
for method in s.methods.iter() {
let (js, ts) = self.generate_function(
pub fn generate_export_for_class(&mut self, class: &str, export: &shared::Export) {
let (js, ts) = if export.method {
self.generate_function(
"",
&method.function.name,
&shared::struct_function_export_name(&s.name, &method.function.name),
&shared::struct_function_export_name(class, &export.function.name),
true,
&method.function.arguments,
method.function.ret.as_ref(),
);
dst.push_str(&js);
dst.push_str("\n");
ts_dst.push_str(&ts);
ts_dst.push_str("\n");
}
dst.push_str("}\n");
ts_dst.push_str("}\n");
self.cx.globals.push_str(&dst);
self.cx.globals.push_str("\n");
self.cx.typescript.push_str(&ts_dst);
self.cx.typescript.push_str("\n");
&export.function,
)
} else {
self.generate_function(
"static",
&shared::struct_function_export_name(class, &export.function.name),
false,
&export.function,
)
};
let class = self.cx.exported_classes.entry(class.to_string())
.or_insert(ExportedClass::default());
class.contents.push_str(&js);
class.contents.push_str("\n");
class.typescript.push_str(&ts);
class.typescript.push_str("\n");
}
fn generate_function(&mut self,
prefix: &str,
name: &str,
wasm_name: &str,
is_method: bool,
arguments: &[shared::Type],
ret: Option<&shared::Type>) -> (String, String) {
let mut dst = format!("{}(", name);
let mut dst_ts = format!("{}(", name);
function: &shared::Function) -> (String, String) {
let mut dst = format!("{}(", function.name);
let mut dst_ts = format!("{}(", function.name);
let mut passed_args = String::new();
let mut arg_conversions = String::new();
let mut destructors = String::new();
@ -701,7 +703,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
passed_args.push_str("this.ptr");
}
for (i, arg) in arguments.iter().enumerate() {
for (i, arg) in function.arguments.iter().enumerate() {
let name = format!("arg{}", i);
if i > 0 {
dst.push_str(", ");
@ -799,25 +801,25 @@ impl<'a, 'b> SubContext<'a, 'b> {
}
dst.push_str(")");
dst_ts.push_str(")");
let convert_ret = match ret {
let convert_ret = match function.ret {
None => {
dst_ts.push_str(": void");
format!("return ret;")
}
Some(&shared::TYPE_NUMBER) => {
Some(shared::TYPE_NUMBER) => {
dst_ts.push_str(": number");
format!("return ret;")
}
Some(&shared::TYPE_BOOLEAN) => {
Some(shared::TYPE_BOOLEAN) => {
dst_ts.push_str(": boolean");
format!("return ret != 0;")
}
Some(&shared::TYPE_JS_OWNED) => {
Some(shared::TYPE_JS_OWNED) => {
dst_ts.push_str(": any");
self.cx.expose_take_object();
format!("return takeObject(ret);")
}
Some(&shared::TYPE_STRING) => {
Some(shared::TYPE_STRING) => {
dst_ts.push_str(": string");
self.cx.expose_get_string_from_wasm();
self.cx.required_internal_exports.insert("__wbindgen_boxed_str_ptr");
@ -831,10 +833,10 @@ impl<'a, 'b> SubContext<'a, 'b> {
return realRet;
")
}
Some(&shared::TYPE_JS_REF) |
Some(&shared::TYPE_BORROWED_STR) => panic!(),
Some(&t) if (t as u32) & shared::TYPE_CUSTOM_REF_FLAG != 0 => panic!(),
Some(custom) => {
Some(shared::TYPE_JS_REF) |
Some(shared::TYPE_BORROWED_STR) => panic!(),
Some(t) if (t as u32) & shared::TYPE_CUSTOM_REF_FLAG != 0 => panic!(),
Some(ref custom) => {
let name = &self.cx.custom_type_names[custom];
dst_ts.push_str(": ");
dst_ts.push_str(name);
@ -881,78 +883,35 @@ impl<'a, 'b> SubContext<'a, 'b> {
}
pub fn generate_import(&mut self, import: &shared::Import) {
let imported_name = format!("import{}", self.cx.imports.len());
self.cx.imports.push_str(&format!("
import {{ {} as {} }} from '{}';
", import.function.name, imported_name, import.module));
let name = shared::mangled_import_name(None, &import.function.name);
self.gen_import_shim(&name,
&imported_name,
false,
import.catch,
&import.function);
self.cx.imports_to_rewrite.insert(name);
}
pub fn generate_import_struct(&mut self, import: &shared::ImportStruct) {
if let Some(ref module) = import.module {
self.cx.imports.push_str(&format!("
import {{ {} }} from '{}';
", import.name, module));
let name_to_import = import.class.as_ref().unwrap_or(&import.function.name);
if self.cx.imported_names.insert(name_to_import.clone()) {
self.cx.imports.push_str(&format!("
import {{ {} }} from '{}';
", name_to_import, module));
}
}
for f in import.functions.iter() {
self.generate_import_struct_function(&import.name, f);
}
}
let name = shared::mangled_import_name(import.class.as_ref().map(|s| &**s),
&import.function.name);
self.cx.imports_to_rewrite.insert(name.clone());
fn generate_import_struct_function(
&mut self,
class: &str,
f: &shared::ImportStructFunction,
) {
let delegate = if f.method {
format!("{}.prototype.{}.call", class, f.function.name)
} else if f.js_new {
format!("new {}", class)
} else {
format!("{}.{}", class, f.function.name)
};
let name = shared::mangled_import_name(Some(class), &f.function.name);
self.gen_import_shim(&name,
&delegate,
f.method,
f.catch,
&f.function);
self.cx.imports_to_rewrite.insert(name);
}
fn gen_import_shim(
&mut self,
shim_name: &str,
shim_delegate: &str,
is_method: bool,
catch: bool,
import: &shared::Function,
) {
let mut dst = String::new();
dst.push_str(&format!("function {}(", shim_name));
dst.push_str(&format!("function {}(", name));
let mut invoc_args = Vec::new();
let mut abi_args = Vec::new();
if is_method {
abi_args.push("ptr".to_string());
invoc_args.push("getObject(ptr)".to_string());
self.cx.expose_get_object();
}
// if import.method {
// abi_args.push("ptr".to_string());
// invoc_args.push("getObject(ptr)".to_string());
// self.cx.expose_get_object();
// }
let mut extra = String::new();
for (i, arg) in import.arguments.iter().enumerate() {
for (i, arg) in import.function.arguments.iter().enumerate() {
match *arg {
shared::TYPE_NUMBER => {
invoc_args.push(format!("arg{}", i));
@ -995,8 +954,21 @@ impl<'a, 'b> SubContext<'a, 'b> {
}
}
let invoc = format!("{}({})", shim_delegate, invoc_args.join(", "));
let invoc = match import.ret {
let invoc_args = invoc_args.join(", ");
let name = &import.function.name;
let invoc = match import.class {
Some(ref class) if import.method => {
format!("{}.prototype.{}.call({})", class, name, invoc_args)
}
Some(ref class) if import.js_new => {
format!("new {}({})", class, invoc_args)
}
Some(ref class) => {
format!("{}.{}({})", class, name, invoc_args)
}
None => format!("{}({})", name, invoc_args),
};
let invoc = match import.function.ret {
Some(shared::TYPE_NUMBER) => format!("return {};", invoc),
Some(shared::TYPE_BOOLEAN) => format!("return {} ? 1 : 0;", invoc),
Some(shared::TYPE_JS_OWNED) => {
@ -1017,7 +989,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
_ => unimplemented!(),
};
let invoc = if catch {
let invoc = if import.catch {
self.cx.expose_uint32_memory();
self.cx.expose_add_heap_object();
abi_args.push("exnptr".to_string());

View File

@ -78,6 +78,8 @@ impl Bindgen {
required_internal_exports: Default::default(),
imports_to_rewrite: Default::default(),
custom_type_names: Default::default(),
imported_names: Default::default(),
exported_classes: Default::default(),
config: &self,
module: &mut module,
};

View File

@ -1,50 +1,50 @@
use std::collections::BTreeSet;
use proc_macro2::Span;
use quote::{Tokens, ToTokens};
use shared;
use syn;
#[derive(Default)]
pub struct Program {
pub structs: Vec<Struct>,
pub free_functions: Vec<Function>,
pub exports: Vec<Export>,
pub imports: Vec<Import>,
pub imported_structs: Vec<ImportStruct>,
pub imported_types: Vec<(syn::Visibility, syn::Ident)>,
pub structs: Vec<Struct>,
}
pub struct Export {
pub class: Option<syn::Ident>,
pub method: bool,
pub mutable: bool,
pub function: Function,
}
pub struct Import {
pub module: Option<String>,
pub kind: ImportKind,
pub function: Function,
}
pub enum ImportKind {
Method { class: String, ty: syn::Type },
Static { class: String, ty: syn::Type },
JsConstructor { class: String, ty: syn::Type },
Normal,
}
pub struct Function {
pub name: syn::Ident,
pub arguments: Vec<Type>,
pub ret: Option<Type>,
}
pub struct Import {
pub module: String,
pub function: ImportFunction,
}
pub struct ImportFunction {
pub catch: bool,
pub ident: syn::Ident,
pub wasm_function: Function,
pub opts: BindgenAttrs,
pub rust_attrs: Vec<syn::Attribute>,
pub rust_decl: Box<syn::FnDecl>,
pub rust_vis: syn::Visibility,
pub rust_attrs: Vec<syn::Attribute>,
}
pub struct ImportStruct {
pub module: Option<String>,
pub struct Struct {
pub name: syn::Ident,
pub functions: Vec<ImportStructFunction>,
}
pub struct ImportStructFunction {
pub kind: ImportFunctionKind,
pub function: ImportFunction,
}
pub enum ImportFunctionKind {
Method,
Static,
JsConstructor,
}
pub enum Type {
@ -57,19 +57,54 @@ pub enum Type {
ByValue(syn::Type),
}
pub struct Struct {
pub name: syn::Ident,
pub methods: Vec<Method>,
pub functions: Vec<Function>,
}
pub struct Method {
pub mutable: bool,
pub function: Function,
}
impl Program {
pub fn push_impl(&mut self, item: &syn::ItemImpl) {
pub fn push_item(&mut self,
item: syn::Item,
opts: Option<BindgenAttrs>,
tokens: &mut Tokens) {
match item {
syn::Item::Fn(mut f) => {
let opts = opts.unwrap_or_else(|| BindgenAttrs::find(&mut f.attrs));
let no_mangle = f.attrs.iter()
.enumerate()
.filter_map(|(i, m)| m.interpret_meta().map(|m| (i, m)))
.find(|&(_, ref m)| m.name() == "no_mangle");
match no_mangle {
Some((i, _)) => { f.attrs.remove(i); }
None => {
panic!("#[wasm_bindgen] can only be applied to #[no_mangle] \
functions, or those that would otherwise be exported")
}
}
f.to_tokens(tokens);
self.exports.push(Export {
class: None,
method: false,
mutable: false,
function: Function::from(f, opts),
});
}
syn::Item::Struct(mut s) => {
let opts = opts.unwrap_or_else(|| BindgenAttrs::find(&mut s.attrs));
s.to_tokens(tokens);
self.structs.push(Struct::from(s, opts));
}
syn::Item::Impl(mut i) => {
let opts = opts.unwrap_or_else(|| BindgenAttrs::find(&mut i.attrs));
i.to_tokens(tokens);
self.push_impl(i, opts);
}
syn::Item::ForeignMod(mut f) => {
let opts = opts.unwrap_or_else(|| BindgenAttrs::find(&mut f.attrs));
self.push_foreign_mod(f, opts);
}
_ => panic!("#[wasm_bindgen] can only be applied to a function, \
struct, impl, or extern block"),
}
}
pub fn push_impl(&mut self, item: syn::ItemImpl, _opts: BindgenAttrs) {
if item.defaultness.is_some() {
panic!("default impls are not supported");
}
@ -91,70 +126,76 @@ impl Program {
}
_ => panic!("unsupported self type in impl"),
};
let dst = self.structs
.iter_mut()
.find(|s| s.name == name)
.expect(&format!("failed to definition of struct for impl of `{}`", name));
for item in item.items.iter() {
dst.push_item(item);
for item in item.items.into_iter() {
self.push_impl_item(name, item);
}
}
pub fn push_foreign_mod(&mut self, f: &syn::ItemForeignMod) {
fn push_impl_item(&mut self, class: syn::Ident, item: syn::ImplItem) {
let mut method = match item {
syn::ImplItem::Const(_) => panic!("const definitions aren't supported"),
syn::ImplItem::Type(_) => panic!("type definitions in impls aren't supported"),
syn::ImplItem::Method(m) => m,
syn::ImplItem::Macro(_) => panic!("macros in impls aren't supported"),
syn::ImplItem::Verbatim(_) => panic!("unparsed impl item?"),
};
match method.vis {
syn::Visibility::Public(_) => {}
_ => return,
}
if method.defaultness.is_some() {
panic!("default methods are not supported");
}
if method.sig.constness.is_some() {
panic!("can only bindgen non-const functions");
}
if method.sig.unsafety.is_some() {
panic!("can only bindgen safe functions");
}
let opts = BindgenAttrs::find(&mut method.attrs);
let (function, mutable) = Function::from_decl(method.sig.ident,
Box::new(method.sig.decl),
method.attrs,
opts,
method.vis,
true);
self.exports.push(Export {
class: Some(class),
method: mutable.is_some(),
mutable: mutable.unwrap_or(false),
function,
});
}
pub fn push_foreign_mod(&mut self, f: syn::ItemForeignMod, opts: BindgenAttrs) {
match f.abi.name {
Some(ref l) if l.value() == "JS" => {}
_ => panic!("only foreign mods with the `JS` ABI are allowed"),
Some(ref l) if l.value() == "C" => {}
None => {}
_ => panic!("only foreign mods with the `C` ABI are allowed"),
}
let module = f.attrs.iter()
.filter_map(|f| f.interpret_meta())
.filter_map(|i| {
match i {
syn::Meta::NameValue(i) => {
if i.ident == "wasm_module" {
Some(i.lit)
} else {
None
}
}
_ => None,
}
})
.next()
.and_then(|lit| {
match lit {
syn::Lit::Str(v) => Some(v.value()),
_ => None,
}
})
.expect("must specify `#[wasm_module = ...]` for module to import from");
for item in f.items.iter() {
let import = self.gen_foreign_item(item, false).0;
self.imports.push(Import {
module: module.clone(),
function: import,
});
for item in f.items.into_iter() {
match item {
syn::ForeignItem::Fn(f) => self.push_foreign_fn(f, &opts),
syn::ForeignItem::Type(t) => self.push_foreign_ty(t, &opts),
_ => panic!("only foreign functions/types allowed for now"),
}
}
}
pub fn gen_foreign_item(&mut self,
f: &syn::ForeignItem,
allow_self: bool) -> (ImportFunction, bool) {
let f = match *f {
syn::ForeignItem::Fn(ref f) => f,
_ => panic!("only foreign functions allowed for now, not statics"),
};
pub fn push_foreign_fn(&mut self,
mut f: syn::ForeignItemFn,
module_opts: &BindgenAttrs) {
let opts = BindgenAttrs::find(&mut f.attrs);
let (mut wasm, mutable) = Function::from_decl(f.ident, &f.decl, allow_self);
let is_method = match mutable {
Some(false) => true,
None => false,
Some(true) => {
panic!("mutable self methods not allowed in extern structs");
}
};
let opts = BindgenOpts::from(&f.attrs);
if opts.catch {
let mut wasm = Function::from_decl(f.ident,
f.decl,
f.attrs,
opts,
f.vis,
false).0;
if wasm.opts.catch() {
// TODO: this assumes a whole bunch:
//
// * The outer type is actually a `Result`
@ -166,38 +207,73 @@ impl Program {
.expect("can't `catch` without returning a Result");
}
(ImportFunction {
rust_attrs: f.attrs.clone(),
rust_vis: f.vis.clone(),
rust_decl: f.decl.clone(),
ident: f.ident.clone(),
wasm_function: wasm,
catch: opts.catch,
}, is_method)
let kind = if wasm.opts.method() {
let class = wasm.arguments.get(0)
.expect("methods must have at least one argument");
let class = match *class {
Type::ByRef(ref t) |
Type::ByValue(ref t) => t,
Type::ByMutRef(_) => {
panic!("first method argument cannot be mutable ref")
}
Type::String | Type::BorrowedStr => {
panic!("method receivers cannot be strings")
}
};
let class_name = match *class {
syn::Type::Path(syn::TypePath { qself: None, ref path }) => path,
_ => panic!("first argument of method must be a path"),
};
let class_name = extract_path_ident(class_name)
.expect("first argument of method must be a bare type");
ImportKind::Method {
class: class_name.as_ref().to_string(),
ty: class.clone(),
}
} else if wasm.opts.constructor() {
let class = match wasm.ret {
Some(Type::ByValue(ref t)) => t,
_ => panic!("constructor returns must be bare types"),
};
let class_name = match *class {
syn::Type::Path(syn::TypePath { qself: None, ref path }) => path,
_ => panic!("first argument of method must be a path"),
};
let class_name = extract_path_ident(class_name)
.expect("first argument of method must be a bare type");
ImportKind::JsConstructor {
class: class_name.as_ref().to_string(),
ty: class.clone(),
}
} else if let Some(class) = wasm.opts.static_receiver() {
let class_name = match *class {
syn::Type::Path(syn::TypePath { qself: None, ref path }) => path,
_ => panic!("first argument of method must be a path"),
};
let class_name = extract_path_ident(class_name)
.expect("first argument of method must be a bare type");
ImportKind::Static {
class: class_name.to_string(),
ty: class.clone(),
}
} else {
ImportKind::Normal
};
self.imports.push(Import {
module: module_opts.module().map(|s| s.to_string()),
kind,
function: wasm,
});
}
pub fn push_extern_class(&mut self, class: &ExternClass) {
let functions = class.functions.iter()
.map(|f| {
let (f, method) = self.gen_foreign_item(f, true);
let kind = if method {
ImportFunctionKind::Method
} else {
let opts = BindgenOpts::from(&f.rust_attrs);
if opts.constructor {
ImportFunctionKind::JsConstructor
} else {
ImportFunctionKind::Static
}
};
ImportStructFunction { kind, function: f }
})
.collect();
self.imported_structs.push(ImportStruct {
module: class.module.as_ref().map(|s| s.value()),
name: class.name,
functions,
});
pub fn push_foreign_ty(&mut self,
f: syn::ForeignItemType,
_module_opts: &BindgenAttrs) {
self.imported_types.push((f.vis, f.ident));
}
pub fn wbg_literal(&self, dst: &mut Tokens) -> usize {
@ -207,16 +283,17 @@ impl Program {
};
a.append("wbg:");
a.fields(&[
("structs", &|a| a.list(&self.structs, Struct::wbg_literal)),
("free_functions", &|a| a.list(&self.free_functions, Function::wbg_literal)),
("exports", &|a| a.list(&self.exports, Export::wbg_literal)),
("imports", &|a| a.list(&self.imports, Import::wbg_literal)),
("imported_structs", &|a| a.list(&self.imported_structs, ImportStruct::wbg_literal)),
("custom_type_names", &|a| {
a.list(&self.structs, |s, a| {
let val = shared::name_to_descriptor(s.name.as_ref());
let names = self.exports.iter()
.filter_map(|e| e.class)
.collect::<BTreeSet<_>>();
a.list(&names, |s, a| {
let val = shared::name_to_descriptor(s.as_ref());
a.fields(&[
("descriptor", &|a| a.char(val)),
("name", &|a| a.str(s.name.as_ref()))
("name", &|a| a.str(s.as_ref()))
]);
})
}),
@ -226,7 +303,7 @@ impl Program {
}
impl Function {
pub fn from(input: &syn::ItemFn) -> Function {
pub fn from(input: syn::ItemFn, opts: BindgenAttrs) -> Function {
match input.vis {
syn::Visibility::Public(_) => {}
_ => panic!("can only bindgen public functions"),
@ -237,18 +314,24 @@ impl Function {
if input.unsafety.is_some() {
panic!("can only bindgen safe functions");
}
if !input.abi.is_none() {
panic!("can only bindgen Rust ABI functions")
}
if !input.abi.is_none() {
panic!("can only bindgen Rust ABI functions")
if input.abi.is_none() {
panic!("can only bindgen `extern` ABI functions, or those that \
would otherwise be exported")
}
Function::from_decl(input.ident, &input.decl, false).0
Function::from_decl(input.ident,
input.decl,
input.attrs,
opts,
input.vis,
false).0
}
pub fn from_decl(name: syn::Ident,
decl: &syn::FnDecl,
decl: Box<syn::FnDecl>,
attrs: Vec<syn::Attribute>,
opts: BindgenAttrs,
vis: syn::Visibility,
allow_self: bool) -> (Function, Option<bool>) {
if decl.variadic.is_some() {
panic!("can't bindgen variadic functions")
@ -281,31 +364,15 @@ impl Function {
syn::ReturnType::Type(_, ref t) => Some(Type::from(t)),
};
(Function { name, arguments, ret }, mutable)
}
pub fn free_function_export_name(&self) -> syn::LitStr {
let name = shared::free_function_export_name(self.name.as_ref());
syn::LitStr::new(&name, Span::def_site())
}
pub fn struct_function_export_name(&self, s: syn::Ident) -> syn::LitStr {
let name = shared::struct_function_export_name(
s.as_ref(),
self.name.as_ref(),
);
syn::LitStr::new(&name, Span::def_site())
}
pub fn rust_symbol(&self, namespace: Option<syn::Ident>) -> syn::Ident {
let mut generated_name = format!("__wasm_bindgen_generated");
if let Some(ns) = namespace {
generated_name.push_str("_");
generated_name.push_str(ns.as_ref());
}
generated_name.push_str("_");
generated_name.push_str(self.name.as_ref());
syn::Ident::from(generated_name)
(Function {
name,
arguments,
ret,
opts,
rust_vis: vis,
rust_decl: decl,
rust_attrs: attrs,
}, mutable)
}
fn wbg_literal(&self, a: &mut LiteralBuilder) {
@ -389,74 +456,68 @@ impl Type {
}
}
impl Struct {
pub fn from(s: &syn::ItemStruct) -> Struct {
Struct {
name: s.ident,
methods: Vec::new(),
functions: Vec::new(),
impl Export {
pub fn rust_symbol(&self) -> syn::Ident {
let mut generated_name = format!("__wasm_bindgen_generated");
if let Some(class) = self.class {
generated_name.push_str("_");
generated_name.push_str(class.as_ref());
}
generated_name.push_str("_");
generated_name.push_str(self.function.name.as_ref());
syn::Ident::from(generated_name)
}
pub fn free_function(&self) -> syn::Ident {
syn::Ident::from(shared::free_function(self.name.as_ref()))
}
pub fn push_item(&mut self, item: &syn::ImplItem) {
let method = match *item {
syn::ImplItem::Const(_) => panic!("const definitions aren't supported"),
syn::ImplItem::Type(_) => panic!("type definitions in impls aren't supported"),
syn::ImplItem::Method(ref m) => m,
syn::ImplItem::Macro(_) => panic!("macros in impls aren't supported"),
syn::ImplItem::Verbatim(_) => panic!("unparsed impl item?"),
};
match method.vis {
syn::Visibility::Public(_) => {}
_ => return,
}
if method.defaultness.is_some() {
panic!("default methods are not supported");
}
if method.sig.constness.is_some() {
panic!("can only bindgen non-const functions");
}
if method.sig.unsafety.is_some() {
panic!("can only bindgen safe functions");
}
let (function, mutable) = Function::from_decl(method.sig.ident,
&method.sig.decl,
true);
match mutable {
Some(mutable) => {
self.methods.push(Method { mutable, function });
pub fn export_name(&self) -> syn::LitStr {
let name = match self.class {
Some(class) => {
shared::struct_function_export_name(
class.as_ref(),
self.function.name.as_ref(),
)
}
None => {
self.functions.push(function);
shared::free_function_export_name(self.function.name.as_ref())
}
}
};
syn::LitStr::new(&name, Span::def_site())
}
fn wbg_literal(&self, a: &mut LiteralBuilder) {
a.fields(&[
("name", &|a| a.str(self.name.as_ref())),
("functions", &|a| a.list(&self.functions, Function::wbg_literal)),
("methods", &|a| a.list(&self.methods, Method::wbg_literal)),
]);
}
}
impl Method {
fn wbg_literal(&self, a: &mut LiteralBuilder) {
a.fields(&[
("mutable", &|a| a.bool(self.mutable)),
("class", &|a| {
match self.class {
Some(ref s) => a.str(s.as_ref()),
None => a.append("null"),
}
}),
("method", &|a| a.bool(self.method)),
("function", &|a| self.function.wbg_literal(a)),
]);
}
}
impl ImportStruct {
impl Import {
fn wbg_literal(&self, a: &mut LiteralBuilder) {
let mut method = false;
let mut js_new = false;
let mut statik = false;
let mut class_name = None;
match self.kind {
ImportKind::Method { ref class, .. } => {
method = true;
class_name = Some(class);
}
ImportKind::JsConstructor { ref class, .. } => {
js_new = true;
class_name = Some(class);
}
ImportKind::Static { ref class, .. } => {
statik = true;
class_name = Some(class);
}
ImportKind::Normal => {}
}
a.fields(&[
("module", &|a| {
match self.module {
@ -464,99 +525,27 @@ impl ImportStruct {
None => a.append("null"),
}
}),
("name", &|a| a.str(self.name.as_ref())),
("functions", &|a| {
a.list(&self.functions,
|f, a| {
let (method, new) = match f.kind {
ImportFunctionKind::Method => (true, false),
ImportFunctionKind::JsConstructor => (false, true),
ImportFunctionKind::Static => (false, false),
};
a.fields(&[
("method", &|a| a.bool(method)),
("js_new", &|a| a.bool(new)),
("catch", &|a| a.bool(f.function.catch)),
("function", &|a| f.function.wasm_function.wbg_literal(a)),
]);
})
("catch", &|a| a.bool(self.function.opts.catch())),
("method", &|a| a.bool(method)),
("js_new", &|a| a.bool(js_new)),
("statik", &|a| a.bool(statik)),
("function", &|a| self.function.wbg_literal(a)),
("class", &|a| {
match class_name {
Some(s) => a.str(s),
None => a.append("null"),
}
}),
]);
}
}
impl Import {
fn wbg_literal(&self, a: &mut LiteralBuilder) {
a.fields(&[
("module", &|a| a.str(&self.module)),
("catch", &|a| a.bool(self.function.catch)),
("function", &|a| self.function.wasm_function.wbg_literal(a)),
]);
impl Struct {
fn from(s: syn::ItemStruct, _opts: BindgenAttrs) -> Struct {
Struct { name: s.ident }
}
}
pub struct File {
pub items: Vec<MyItem>,
}
impl syn::synom::Synom for File {
named!(parse -> Self, map!(many0!(syn!(MyItem)), |items| File { items }));
}
pub enum MyItem {
Normal(syn::Item),
ExternClass(ExternClass),
}
impl syn::synom::Synom for MyItem {
named!(parse -> Self, alt!(
syn!(syn::Item) => { MyItem::Normal }
|
syn!(ExternClass) => { MyItem::ExternClass }
));
}
pub struct ExternClass {
name: syn::Ident,
module: Option<syn::LitStr>,
functions: Vec<syn::ForeignItem>,
}
impl syn::synom::Synom for ExternClass {
named!(parse -> Self, do_parse!(
module: option!(do_parse!(
punct!(#) >>
name: brackets!(do_parse!(
call!(term, "wasm_module") >>
punct!(=) >>
val: syn!(syn::LitStr) >>
(val)
)) >>
(name.1)
)) >>
keyword!(extern) >>
keyword!(struct) >>
name: syn!(syn::Ident) >>
items: braces!(many0!(syn!(syn::ForeignItem))) >>
(ExternClass {
name,
module,
functions: items.1,
})
));
}
fn term<'a>(cursor: syn::buffer::Cursor<'a>, name: &str)
-> syn::synom::PResult<'a, ()>
{
if let Some((_span, term, next)) = cursor.term() {
if term.as_str() == name {
return Ok(((), next))
}
}
syn::parse_error()
}
struct LiteralBuilder<'a> {
dst: &'a mut Tokens,
cnt: usize,
@ -634,51 +623,124 @@ impl<'a> LiteralBuilder<'a> {
}
#[derive(Default)]
struct BindgenOpts {
catch: bool,
constructor: bool,
pub struct BindgenAttrs {
attrs: Vec<BindgenAttr>,
}
impl BindgenOpts {
fn from(attrs: &[syn::Attribute]) -> BindgenOpts {
let mut opts = BindgenOpts::default();
let attrs = attrs.iter()
.filter_map(|a| a.interpret_meta())
.filter_map(|m| {
match m {
syn::Meta::List(i) => {
if i.ident == "wasm_bindgen" {
Some(i.nested)
} else {
None
}
}
impl BindgenAttrs {
pub fn find(attrs: &mut Vec<syn::Attribute>) -> BindgenAttrs {
let pos = attrs.iter()
.enumerate()
.find(|&(_, ref m)| m.path.segments[0].ident == "wasm_bindgen")
.map(|a| a.0);
let pos = match pos {
Some(i) => i,
None => return BindgenAttrs::default(),
};
syn::parse(attrs.remove(pos).tts.into())
.expect("malformed #[wasm_bindgen] attribute")
}
fn module(&self) -> Option<&str> {
self.attrs.iter()
.filter_map(|a| {
match *a {
BindgenAttr::Module(ref s) => Some(&s[..]),
_ => None,
}
})
.flat_map(|a| a)
.next()
}
pub fn catch(&self) -> bool {
self.attrs.iter()
.any(|a| {
match *a {
BindgenAttr::Catch => true,
_ => false,
}
})
}
fn constructor(&self) -> bool {
self.attrs.iter()
.any(|a| {
match *a {
BindgenAttr::Constructor => true,
_ => false,
}
})
}
fn method(&self) -> bool {
self.attrs.iter()
.any(|a| {
match *a {
BindgenAttr::Method => true,
_ => false,
}
})
}
fn static_receiver(&self) -> Option<&syn::Type> {
self.attrs.iter()
.filter_map(|a| {
match a {
syn::NestedMeta::Meta(a) => Some(a),
match *a {
BindgenAttr::Static(ref s) => Some(s),
_ => None,
}
});
for attr in attrs {
match attr {
syn::Meta::Word(a) => {
if a == "constructor" {
opts.constructor = true;
} else if a == "catch" {
opts.catch = true;
}
}
_ => {}
}
}
return opts
})
.next()
}
}
impl syn::synom::Synom for BindgenAttrs {
named!(parse -> Self, alt!(
do_parse!(
opts: parens!(call!(
syn::punctuated::Punctuated::<_, syn::token::Comma>::parse_terminated
)) >>
(BindgenAttrs {
attrs: opts.1.into_iter().collect(),
})
) => { |s| s }
|
epsilon!() => { |_| BindgenAttrs { attrs: Vec::new() } }
));
}
enum BindgenAttr {
Catch,
Constructor,
Method,
Static(syn::Type),
Module(String),
}
impl syn::synom::Synom for BindgenAttr {
named!(parse -> Self, alt!(
call!(term, "catch") => { |_| BindgenAttr::Catch }
|
call!(term, "constructor") => { |_| BindgenAttr::Constructor }
|
call!(term, "method") => { |_| BindgenAttr::Method }
|
do_parse!(
call!(term, "static") >>
punct!(=) >>
s: syn!(syn::Type) >>
(s)
)=> { BindgenAttr::Static }
|
do_parse!(
call!(term, "module") >>
punct!(=) >>
s: syn!(syn::LitStr) >>
(s.value())
)=> { BindgenAttr::Module }
));
}
fn extract_first_ty_param(ty: Option<&Type>) -> Option<Option<Type>> {
let ty = match ty {
Some(t) => t,
@ -707,3 +769,14 @@ fn extract_first_ty_param(ty: Option<&Type>) -> Option<Option<Type>> {
}
Some(Some(Type::from(ty)))
}
fn term<'a>(cursor: syn::buffer::Cursor<'a>, name: &str)
-> syn::synom::PResult<'a, ()>
{
if let Some((_span, term, next)) = cursor.term() {
if term.as_str() == name {
return Ok(((), next))
}
}
syn::parse_error()
}

View File

@ -13,7 +13,7 @@ extern crate wasm_bindgen_shared as shared;
use std::sync::atomic::*;
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenNode, Delimiter, TokenTree};
use proc_macro2::Span;
use quote::{Tokens, ToTokens};
macro_rules! my_quote {
@ -22,75 +22,41 @@ macro_rules! my_quote {
mod ast;
#[proc_macro]
pub fn wasm_bindgen(input: TokenStream) -> TokenStream {
// Parse the input as a list of Rust items, reusing the `syn::File` parser.
let file = syn::parse::<ast::File>(input)
.expect("expected a set of valid Rust items");
#[proc_macro_attribute]
pub fn wasm_bindgen(attr: TokenStream, input: TokenStream) -> TokenStream {
let item = syn::parse::<syn::Item>(input.clone())
.expect("expected a valid Rust item");
let opts = syn::parse::<ast::BindgenAttrs>(attr)
.expect("invalid arguments to #[wasm_bindgen]");
let mut ret = Tokens::new();
let mut program = ast::Program::default();
program.push_item(item, Some(opts), &mut ret);
generate_wrappers(program, &mut ret);
let mut program = ast::Program {
structs: Vec::new(),
free_functions: Vec::new(),
imports: Vec::new(),
imported_structs: Vec::new(),
};
// println!("{}", ret);
// Translate all input items into our own internal representation (the `ast`
// module). We'll be panicking here on anything that we can't process
ret.into()
}
for item in file.items.iter() {
let item = match *item {
ast::MyItem::ExternClass(ref c) => {
program.push_extern_class(c);
continue
}
ast::MyItem::Normal(ref item) => item,
};
match *item {
syn::Item::Fn(ref f) => {
item.to_tokens(&mut ret);
program.free_functions.push(ast::Function::from(f));
}
syn::Item::Struct(ref s) => {
item.to_tokens(&mut ret);
let s = ast::Struct::from(s);
if program.structs.iter().any(|a| a.name == s.name) {
panic!("redefinition of struct: {}", s.name);
}
program.structs.push(s);
}
syn::Item::Impl(ref i) => {
item.to_tokens(&mut ret);
program.push_impl(i);
}
syn::Item::ForeignMod(ref f) => {
program.push_foreign_mod(f);
}
_ => panic!("unexpected item in bindgen macro"),
}
}
// Generate wrappers for all the items that we've found
for function in program.free_functions.iter() {
bindgen_fn(function, &mut ret);
// Generate wrappers for all the items that we've found
fn generate_wrappers(program: ast::Program, tokens: &mut Tokens) {
for export in program.exports.iter() {
bindgen_export(export, tokens);
}
for s in program.structs.iter() {
bindgen_struct(s, &mut ret);
bindgen_struct(s, tokens);
}
for i in program.imports.iter() {
bindgen_import(i, &mut ret);
bindgen_import(i, tokens);
}
for i in program.imported_structs.iter() {
bindgen_imported_struct(i, &mut ret);
for &(ref vis, ref t) in program.imported_types.iter() {
bindgen_imported_type(vis, t, tokens);
}
// Finally generate a static which will eventually be what lives in a custom
// section of the wasm executable. For now it's just a plain old static, but
// we'll eventually have it actually in its own section.
// Generate a static which will eventually be what lives in a custom section
// of the wasm executable. For now it's just a plain old static, but we'll
// eventually have it actually in its own section.
static CNT: AtomicUsize = ATOMIC_USIZE_INIT;
let generated_static_name = format!("__WASM_BINDGEN_GENERATED{}",
@ -104,32 +70,12 @@ pub fn wasm_bindgen(input: TokenStream) -> TokenStream {
#[allow(non_upper_case_globals)]
pub static #generated_static_name: [u32; #generated_static_length] =
[#generated_static_value];
}).to_tokens(&mut ret);
// println!("{}", ret);
ret.into()
}
fn bindgen_fn(function: &ast::Function, into: &mut Tokens) {
bindgen(&function.free_function_export_name(),
function.rust_symbol(None),
Receiver::FreeFunction(function.name),
&function.arguments,
function.ret.as_ref(),
into)
}).to_tokens(tokens);
}
fn bindgen_struct(s: &ast::Struct, into: &mut Tokens) {
for f in s.functions.iter() {
bindgen_struct_fn(s, f, into);
}
for f in s.methods.iter() {
bindgen_struct_method(s, f, into);
}
let name = &s.name;
let free_fn = s.free_function();
let free_fn = syn::Ident::from(shared::free_function(s.name.as_ref()));
let c = shared::name_to_descriptor(name.as_ref()) as u32;
(my_quote! {
impl ::wasm_bindgen::convert::WasmBoundary for #name {
@ -175,43 +121,17 @@ fn bindgen_struct(s: &ast::Struct, into: &mut Tokens) {
}).to_tokens(into);
}
fn bindgen_struct_fn(s: &ast::Struct, f: &ast::Function, into: &mut Tokens) {
bindgen(&f.struct_function_export_name(s.name),
f.rust_symbol(Some(s.name)),
Receiver::StructFunction(s.name, f.name),
&f.arguments,
f.ret.as_ref(),
into)
}
fn bindgen_struct_method(s: &ast::Struct, m: &ast::Method, into: &mut Tokens) {
bindgen(&m.function.struct_function_export_name(s.name),
m.function.rust_symbol(Some(s.name)),
Receiver::StructMethod(s.name, m.mutable, m.function.name),
&m.function.arguments,
m.function.ret.as_ref(),
into)
}
enum Receiver {
FreeFunction(syn::Ident),
StructFunction(syn::Ident, syn::Ident),
StructMethod(syn::Ident, bool, syn::Ident),
}
fn bindgen(export_name: &syn::LitStr,
generated_name: syn::Ident,
receiver: Receiver,
arguments: &[ast::Type],
ret_type: Option<&ast::Type>,
into: &mut Tokens) {
fn bindgen_export(export: &ast::Export, into: &mut Tokens) {
let generated_name = export.rust_symbol();
let export_name = export.export_name();
let mut args = vec![];
let mut arg_conversions = vec![];
let mut converted_arguments = vec![];
let ret = syn::Ident::from("_ret");
let mut offset = 0;
if let Receiver::StructMethod(class, _, _) = receiver {
if export.method {
let class = export.class.unwrap();
args.push(my_quote! { me: *mut ::wasm_bindgen::__rt::WasmRefCell<#class> });
arg_conversions.push(my_quote! {
::wasm_bindgen::__rt::assert_not_null(me);
@ -220,7 +140,7 @@ fn bindgen(export_name: &syn::LitStr,
offset = 1;
}
for (i, ty) in arguments.iter().enumerate() {
for (i, ty) in export.function.arguments.iter().enumerate() {
let i = i + offset;
let ident = syn::Ident::from(format!("arg{}", i));
match *ty {
@ -288,12 +208,12 @@ fn bindgen(export_name: &syn::LitStr,
}
let ret_ty;
let convert_ret;
match ret_type {
Some(&ast::Type::String) => {
match export.function.ret {
Some(ast::Type::String) => {
ret_ty = my_quote! { -> *mut String };
convert_ret = my_quote! { Box::into_raw(Box::new(#ret)) };
}
Some(&ast::Type::ByValue(ref t)) => {
Some(ast::Type::ByValue(ref t)) => {
ret_ty = my_quote! {
-> <#t as ::wasm_bindgen::convert::WasmBoundary>::Js
};
@ -301,9 +221,9 @@ fn bindgen(export_name: &syn::LitStr,
<#t as ::wasm_bindgen::convert::WasmBoundary>::into_js(#ret)
};
}
Some(&ast::Type::BorrowedStr) |
Some(&ast::Type::ByMutRef(_)) |
Some(&ast::Type::ByRef(_)) => {
Some(ast::Type::BorrowedStr) |
Some(ast::Type::ByMutRef(_)) |
Some(ast::Type::ByRef(_)) => {
panic!("can't return a borrowed ref");
}
None => {
@ -312,6 +232,19 @@ fn bindgen(export_name: &syn::LitStr,
}
}
let name = export.function.name;
let receiver = match export.class {
Some(_) if export.method => {
if export.mutable {
my_quote! { me.borrow_mut().#name }
} else {
my_quote! { me.borrow().#name }
}
}
Some(class) => my_quote! { #class::#name },
None => my_quote!{ #name },
};
let tokens = my_quote! {
#[export_name = #export_name]
#[allow(non_snake_case)]
@ -324,65 +257,14 @@ fn bindgen(export_name: &syn::LitStr,
tokens.to_tokens(into);
}
impl ToTokens for Receiver {
fn to_tokens(&self, tokens: &mut Tokens) {
match *self {
Receiver::FreeFunction(name) => name.to_tokens(tokens),
Receiver::StructFunction(s, name) => {
s.to_tokens(tokens);
syn::token::Colon2::default().to_tokens(tokens);
name.to_tokens(tokens);
}
Receiver::StructMethod(_, mutable, name) => {
(my_quote! { me }).to_tokens(tokens);
syn::token::Dot::default().to_tokens(tokens);
if mutable {
syn::Ident::from("borrow_mut").to_tokens(tokens);
} else {
syn::Ident::from("borrow").to_tokens(tokens);
}
tokens.append(TokenTree {
span: Span::def_site(),
kind: TokenNode::Group(Delimiter::Parenthesis,
proc_macro2::TokenStream::empty()),
});
syn::token::Dot::default().to_tokens(tokens);
name.to_tokens(tokens);
}
}
}
}
fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) {
let import_name = shared::mangled_import_name(
None,
import.function.wasm_function.name.as_ref(),
);
bindgen_import_function(&import.function, &import_name, tokens);
}
fn bindgen_imported_struct(import: &ast::ImportStruct, tokens: &mut Tokens) {
let name = import.name;
let mut methods = Tokens::new();
for f in import.functions.iter() {
let import_name = shared::mangled_import_name(
Some(&import.name.to_string()),
f.function.wasm_function.name.as_ref(),
);
bindgen_import_function(&f.function, &import_name, &mut methods);
}
fn bindgen_imported_type(vis: &syn::Visibility,
name: &syn::Ident,
tokens: &mut Tokens) {
(my_quote! {
pub struct #name {
#vis struct #name {
obj: ::wasm_bindgen::JsValue,
}
impl #name {
#methods
}
impl ::wasm_bindgen::convert::WasmBoundary for #name {
type Js = <::wasm_bindgen::JsValue as
::wasm_bindgen::convert::WasmBoundary>::Js;
@ -397,42 +279,50 @@ fn bindgen_imported_struct(import: &ast::ImportStruct, tokens: &mut Tokens) {
#name { obj: ::wasm_bindgen::JsValue::from_js(js) }
}
}
impl ::wasm_bindgen::convert::ToRefWasmBoundary for #name {
fn to_js_ref(&self) -> u32 {
self.obj.to_js_ref()
}
}
}).to_tokens(tokens);
}
fn bindgen_import_function(import: &ast::ImportFunction,
import_name: &str,
tokens: &mut Tokens) {
let vis = &import.rust_vis;
let ret = &import.rust_decl.output;
let fn_token = &import.rust_decl.fn_token;
let arguments = &import.rust_decl.inputs;
fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) {
let mut class_ty = None;
let mut is_method = false;
let mut class_name = None;
match import.kind {
ast::ImportKind::Method { ref ty, ref class } => {
is_method = true;
class_ty = Some(ty);
class_name = Some(class);
}
ast::ImportKind::Static { ref ty, ref class } |
ast::ImportKind::JsConstructor { ref ty, ref class } => {
class_ty = Some(ty);
class_name = Some(class);
}
ast::ImportKind::Normal => {}
}
let import_name = shared::mangled_import_name(
class_name.map(|s| &**s),
import.function.name.as_ref(),
);
let vis = &import.function.rust_vis;
let ret = &import.function.rust_decl.output;
let fn_token = &import.function.rust_decl.fn_token;
let mut abi_argument_names = Vec::new();
let mut abi_arguments = Vec::new();
let mut arg_conversions = Vec::new();
let ret_ident = syn::Ident::from("_ret");
let inputs = import.rust_decl.inputs.iter().collect::<Vec<_>>();
let (is_method, inputs) = match inputs.get(0) {
Some(&&syn::FnArg::Captured(_)) => (false, &inputs[..]),
Some(_) => (true, &inputs[1..]),
None => (false, &inputs[..]),
};
if is_method {
let ptr = syn::Ident::from("ptr");
abi_argument_names.push(ptr);
abi_arguments.push(my_quote! { #ptr: u32 });
arg_conversions.push(my_quote! {
let #ptr = ::wasm_bindgen::convert::ToRefWasmBoundary::to_js_ref(&self.obj);
});
}
let names = inputs
let names = import.function.rust_decl.inputs
.iter()
.map(|arg| {
match **arg {
match *arg {
syn::FnArg::Captured(ref c) => c,
_ => panic!("arguments cannot be `self` or ignored"),
}
@ -451,7 +341,7 @@ fn bindgen_import_function(import: &ast::ImportFunction,
}
});
for (ty, name) in import.wasm_function.arguments.iter().zip(names) {
for (i, (ty, name)) in import.function.arguments.iter().zip(names).enumerate() {
match *ty {
ast::Type::BorrowedStr => {
let ptr = syn::Ident::from(format!("{}_ptr", name));
@ -470,19 +360,33 @@ fn bindgen_import_function(import: &ast::ImportFunction,
abi_arguments.push(my_quote! {
#name: <#t as ::wasm_bindgen::convert::WasmBoundary>::Js
});
arg_conversions.push(my_quote! {
let #name = <#t as ::wasm_bindgen::convert::WasmBoundary>
::into_js(#name);
});
if i == 0 && is_method {
arg_conversions.push(my_quote! {
let #name = <#t as ::wasm_bindgen::convert::WasmBoundary>
::into_js(self);
});
} else {
arg_conversions.push(my_quote! {
let #name = <#t as ::wasm_bindgen::convert::WasmBoundary>
::into_js(#name);
});
}
}
ast::Type::ByMutRef(_) => panic!("urgh mut"),
ast::Type::ByRef(ref t) => {
abi_argument_names.push(name);
abi_arguments.push(my_quote! { #name: u32 });
arg_conversions.push(my_quote! {
let #name = <#t as ::wasm_bindgen::convert::ToRefWasmBoundary>
::to_js_ref(#name);
});
if i == 0 && is_method {
arg_conversions.push(my_quote! {
let #name = <#t as ::wasm_bindgen::convert::ToRefWasmBoundary>
::to_js_ref(self);
});
} else {
arg_conversions.push(my_quote! {
let #name = <#t as ::wasm_bindgen::convert::ToRefWasmBoundary>
::to_js_ref(#name);
});
}
}
// TODO: need to test this
ast::Type::String => {
@ -502,7 +406,7 @@ fn bindgen_import_function(import: &ast::ImportFunction,
}
let abi_ret;
let mut convert_ret;
match import.wasm_function.ret {
match import.function.ret {
Some(ast::Type::ByValue(ref t)) => {
abi_ret = my_quote! {
<#t as ::wasm_bindgen::convert::WasmBoundary>::Js
@ -539,7 +443,7 @@ fn bindgen_import_function(import: &ast::ImportFunction,
}
let mut exceptional_ret = my_quote! {};
if import.catch {
if import.function.opts.catch() {
let exn_data = syn::Ident::from("exn_data");
let exn_data_ptr = syn::Ident::from("exn_data_ptr");
abi_argument_names.push(exn_data_ptr);
@ -557,10 +461,24 @@ fn bindgen_import_function(import: &ast::ImportFunction,
};
}
let name = import.ident;
let name = import.function.name;
let import_name = syn::Ident::from(import_name);
(quote! {
#vis #fn_token #name(#arguments) #ret {
let attrs = &import.function.rust_attrs;
let arguments = import.function.rust_decl.inputs
.iter()
.skip(if is_method { 1 } else { 0 })
.collect::<Vec<_>>();
let me = if is_method {
my_quote! { &self, }
} else {
quote!()
};
let invocation = quote! {
#(#attrs)*
#vis extern #fn_token #name(#me #(#arguments),*) #ret {
extern {
fn #import_name(#(#abi_arguments),*) -> #abi_ret;
}
@ -571,5 +489,15 @@ fn bindgen_import_function(import: &ast::ImportFunction,
#convert_ret
}
}
}).to_tokens(tokens);
};
if let Some(class) = class_ty {
(quote! {
impl #class {
#invocation
}
}).to_tokens(tokens);
} else {
invocation.to_tokens(tokens);
}
}

View File

@ -5,58 +5,39 @@ extern crate fnv;
use std::char;
use std::hash::{Hash, Hasher};
#[derive(Serialize, Deserialize)]
#[derive(Deserialize)]
pub struct Program {
pub structs: Vec<Struct>,
pub free_functions: Vec<Function>,
pub exports: Vec<Export>,
pub imports: Vec<Import>,
pub imported_structs: Vec<ImportStruct>,
pub custom_type_names: Vec<CustomTypeName>,
}
#[derive(Serialize, Deserialize)]
pub struct Struct {
pub name: String,
pub functions: Vec<Function>,
pub methods: Vec<Method>,
}
#[derive(Serialize, Deserialize)]
#[derive(Deserialize)]
pub struct Import {
pub module: String,
pub catch: bool,
pub function: Function,
}
#[derive(Serialize, Deserialize)]
pub struct ImportStruct {
pub module: Option<String>,
pub name: String,
pub functions: Vec<ImportStructFunction>,
}
#[derive(Serialize, Deserialize)]
pub struct ImportStructFunction {
pub catch: bool,
pub method: bool,
pub js_new: bool,
pub catch: bool,
pub statik: bool,
pub class: Option<String>,
pub function: Function,
}
#[derive(Serialize, Deserialize)]
pub struct Method {
pub mutable: bool,
#[derive(Deserialize)]
pub struct Export {
pub class: Option<String>,
pub method: bool,
pub function: Function,
}
#[derive(Serialize, Deserialize)]
#[derive(Deserialize)]
pub struct Function {
pub name: String,
pub arguments: Vec<Type>,
pub ret: Option<Type>,
}
#[derive(Serialize, Deserialize)]
#[derive(Deserialize)]
pub struct CustomTypeName {
pub descriptor: char,
pub name: String,

View File

@ -1,8 +1,9 @@
//! Runtime support for the `wasm-bindgen` tool
//!
//! This crate contains the runtime support necessary for `wasm-bindgen` the
//! macro and tool. Crates pull in the `wasm_bindgen!` macro through this crate
//! and this crate also provides JS bindings through the `JsValue` interface.
//! attribute and tool. Crates pull in the `#[wasm_bindgen]` attribute through
//! this crate and this crate also provides JS bindings through the `JsValue`
//! interface.
#![feature(use_extern_macros)]

View File

@ -10,90 +10,118 @@ fn works() {
use wasm_bindgen::prelude::*;
wasm_bindgen! {
pub fn foo() -> JsValue {
JsValue::from("foo")
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn foo() -> JsValue {
JsValue::from("foo")
}
pub fn bar(s: &str) -> JsValue {
JsValue::from(s)
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn bar(s: &str) -> JsValue {
JsValue::from(s)
}
pub fn baz() -> JsValue {
JsValue::from(1.0)
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn baz() -> JsValue {
JsValue::from(1.0)
}
pub fn baz2(a: &JsValue, b: &JsValue) {
assert_eq!(a.as_f64(), Some(2.0));
assert_eq!(b.as_f64(), None);
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn baz2(a: &JsValue, b: &JsValue) {
assert_eq!(a.as_f64(), Some(2.0));
assert_eq!(b.as_f64(), None);
}
pub fn js_null() -> JsValue {
JsValue::null()
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn js_null() -> JsValue {
JsValue::null()
}
pub fn js_undefined() -> JsValue {
JsValue::undefined()
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn js_undefined() -> JsValue {
JsValue::undefined()
}
pub fn test_is_null_undefined(
a: &JsValue,
b: &JsValue,
c: &JsValue,
) {
assert!(a.is_null());
assert!(!a.is_undefined());
#[wasm_bindgen]
#[no_mangle]
pub extern fn test_is_null_undefined(
a: &JsValue,
b: &JsValue,
c: &JsValue,
) {
assert!(a.is_null());
assert!(!a.is_undefined());
assert!(!b.is_null());
assert!(b.is_undefined());
assert!(!b.is_null());
assert!(b.is_undefined());
assert!(!c.is_null());
assert!(!c.is_undefined());
}
assert!(!c.is_null());
assert!(!c.is_undefined());
}
pub fn get_true() -> JsValue {
JsValue::from(true)
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn get_true() -> JsValue {
JsValue::from(true)
}
pub fn get_false() -> JsValue {
JsValue::from(false)
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn get_false() -> JsValue {
JsValue::from(false)
}
pub fn test_bool(
a: &JsValue,
b: &JsValue,
c: &JsValue,
) {
assert_eq!(a.as_bool(), Some(true));
assert_eq!(b.as_bool(), Some(false));
assert_eq!(c.as_bool(), None);
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn test_bool(
a: &JsValue,
b: &JsValue,
c: &JsValue,
) {
assert_eq!(a.as_bool(), Some(true));
assert_eq!(b.as_bool(), Some(false));
assert_eq!(c.as_bool(), None);
}
pub fn mk_symbol() -> JsValue {
let a = JsValue::symbol(None);
assert!(a.is_symbol());
return a
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn mk_symbol() -> JsValue {
let a = JsValue::symbol(None);
assert!(a.is_symbol());
return a
}
pub fn mk_symbol2(s: &str) -> JsValue {
let a = JsValue::symbol(Some(s));
assert!(a.is_symbol());
return a
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn mk_symbol2(s: &str) -> JsValue {
let a = JsValue::symbol(Some(s));
assert!(a.is_symbol());
return a
}
pub fn assert_symbols(a: &JsValue, b: &JsValue) {
assert!(a.is_symbol());
assert!(!b.is_symbol());
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn assert_symbols(a: &JsValue, b: &JsValue) {
assert!(a.is_symbol());
assert!(!b.is_symbol());
}
pub fn acquire_string(a: &JsValue, b: &JsValue) {
assert_eq!(a.as_string().unwrap(), "foo");
assert_eq!(b.as_string(), None);
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn acquire_string(a: &JsValue, b: &JsValue) {
assert_eq!(a.as_string().unwrap(), "foo");
assert_eq!(b.as_string(), None);
}
pub fn acquire_string2(a: &JsValue) -> String {
a.as_string().unwrap_or("wrong".to_string())
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn acquire_string2(a: &JsValue) -> String {
a.as_string().unwrap_or("wrong".to_string())
}
"#)
.file("test.ts", r#"

View File

@ -10,24 +10,24 @@ fn simple() {
use wasm_bindgen::prelude::*;
wasm_bindgen! {
pub struct Foo {
contents: u32,
#[wasm_bindgen]
pub struct Foo {
contents: u32,
}
#[wasm_bindgen]
impl Foo {
pub fn new() -> Foo {
Foo::with_contents(0)
}
impl Foo {
pub fn new() -> Foo {
Foo::with_contents(0)
}
pub fn with_contents(a: u32) -> Foo {
Foo { contents: a }
}
pub fn with_contents(a: u32) -> Foo {
Foo { contents: a }
}
pub fn add(&mut self, amt: u32) -> u32 {
self.contents += amt;
self.contents
}
pub fn add(&mut self, amt: u32) -> u32 {
self.contents += amt;
self.contents
}
}
"#)
@ -62,33 +62,35 @@ fn strings() {
use wasm_bindgen::prelude::*;
wasm_bindgen! {
pub struct Foo {
name: u32,
#[wasm_bindgen]
pub struct Foo {
name: u32,
}
#[wasm_bindgen]
pub struct Bar {
contents: String,
}
#[wasm_bindgen]
impl Foo {
pub fn new() -> Foo {
Foo { name: 0 }
}
pub struct Bar {
contents: String,
pub fn set(&mut self, amt: u32) {
self.name = amt;
}
impl Foo {
pub fn new() -> Foo {
Foo { name: 0 }
}
pub fn set(&mut self, amt: u32) {
self.name = amt;
}
pub fn bar(&self, mix: &str) -> Bar {
Bar { contents: format!("foo-{}-{}", mix, self.name) }
}
pub fn bar(&self, mix: &str) -> Bar {
Bar { contents: format!("foo-{}-{}", mix, self.name) }
}
}
impl Bar {
pub fn name(&self) -> String {
self.contents.clone()
}
#[wasm_bindgen]
impl Bar {
pub fn name(&self) -> String {
self.contents.clone()
}
}
"#)
@ -118,29 +120,31 @@ fn exceptions() {
use wasm_bindgen::prelude::*;
wasm_bindgen! {
pub struct A {
#[wasm_bindgen]
pub struct A {
}
#[wasm_bindgen]
impl A {
pub fn new() -> A {
A {}
}
impl A {
pub fn new() -> A {
A {}
}
pub fn foo(&self, _: &A) {
}
pub fn bar(&mut self, _: &mut A) {
}
pub fn foo(&self, _: &A) {
}
pub struct B {
pub fn bar(&mut self, _: &mut A) {
}
}
impl B {
pub fn new() -> B {
B {}
}
#[wasm_bindgen]
pub struct B {
}
#[wasm_bindgen]
impl B {
pub fn new() -> B {
B {}
}
}
"#)
@ -181,27 +185,29 @@ fn pass_one_to_another() {
use wasm_bindgen::prelude::*;
wasm_bindgen! {
pub struct A {}
#[wasm_bindgen]
pub struct A {}
impl A {
pub fn new() -> A {
A {}
}
pub fn foo(&self, _other: &B) {
}
pub fn bar(&self, _other: B) {
}
#[wasm_bindgen]
impl A {
pub fn new() -> A {
A {}
}
pub struct B {}
pub fn foo(&self, _other: &B) {
}
impl B {
pub fn new() -> B {
B {}
}
pub fn bar(&self, _other: B) {
}
}
#[wasm_bindgen]
pub struct B {}
#[wasm_bindgen]
impl B {
pub fn new() -> B {
B {}
}
}
"#)
@ -218,46 +224,3 @@ fn pass_one_to_another() {
"#)
.test();
}
#[test]
fn bindgen_twice() {
test_support::project()
.file("src/lib.rs", r#"
#![feature(proc_macro)]
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
wasm_bindgen! {
pub struct A {}
impl A {
pub fn new() -> A {
A {}
}
}
}
wasm_bindgen! {
pub struct B {}
impl B {
pub fn new(a: &A) -> B {
B {}
}
}
}
"#)
.file("test.ts", r#"
import { A, B } from "./out";
export function test() {
let a = A.new();
let b = B.new(a);
a.free();
b.free();
}
"#)
.test();
}

View File

@ -10,19 +10,25 @@ fn simple() {
use wasm_bindgen::prelude::*;
wasm_bindgen! {
extern struct Math {
fn random() -> f64;
fn log(a: f64) -> f64;
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn get_random() -> f64 {
Math::random()
}
pub fn get_random() -> f64 {
Math::random()
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn do_log(a: f64) -> f64 {
Math::log(a)
}
pub fn do_log(a: f64) -> f64 {
Math::log(a)
}
#[wasm_bindgen]
extern {
type Math;
#[wasm_bindgen(static = Math)]
fn random() -> f64;
#[wasm_bindgen(static = Math)]
fn log(a: f64) -> f64;
}
"#)
.file("test.ts", r#"
@ -47,15 +53,17 @@ fn import_class() {
use wasm_bindgen::prelude::*;
wasm_bindgen! {
#[wasm_module = "./test"]
extern struct Foo {
fn bar();
}
#[wasm_bindgen(module = "./test")]
extern {
type Foo;
#[wasm_bindgen(static = Foo)]
fn bar();
}
pub fn bar() {
Foo::bar();
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn bar() {
Foo::bar();
}
"#)
.file("test.ts", r#"
@ -88,21 +96,26 @@ fn construct() {
use wasm_bindgen::prelude::*;
wasm_bindgen! {
#[wasm_module = "./test"]
extern struct Foo {
fn create() -> Foo;
fn get_internal_string(&self) -> String;
fn append_to_internal_string(&self, s: &str);
fn assert_internal_string(&self, s: &str);
}
#[wasm_bindgen(module = "./test")]
extern {
type Foo;
#[wasm_bindgen(static = Foo)]
fn create() -> Foo;
#[wasm_bindgen(method)]
fn get_internal_string(this: &Foo) -> String;
#[wasm_bindgen(method)]
fn append_to_internal_string(this: &Foo, s: &str);
#[wasm_bindgen(method)]
fn assert_internal_string(this: &Foo, s: &str);
}
pub fn run() {
let f = Foo::create();
assert_eq!(f.get_internal_string(), "this");
f.append_to_internal_string(" foo");
f.assert_internal_string("this foo");
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn run() {
let f = Foo::create();
assert_eq!(f.get_internal_string(), "this");
f.append_to_internal_string(" foo");
f.assert_internal_string("this foo");
}
"#)
.file("test.ts", r#"
@ -152,18 +165,20 @@ fn new_constructors() {
use wasm_bindgen::prelude::*;
wasm_bindgen! {
#[wasm_module = "./test"]
extern struct Foo {
#[wasm_bindgen(constructor)]
fn new(arg: i32) -> Foo;
fn get(&self) -> i32;
}
#[wasm_bindgen(module = "./test")]
extern {
type Foo;
#[wasm_bindgen(constructor)]
fn new(arg: i32) -> Foo;
#[wasm_bindgen(method)]
fn get(this: &Foo) -> i32;
}
pub fn run() {
let f = Foo::new(1);
assert_eq!(f.get(), 2);
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn run() {
let f = Foo::new(1);
assert_eq!(f.get(), 2);
}
"#)
.file("test.ts", r#"

View File

@ -10,27 +10,36 @@ fn simple() {
use wasm_bindgen::prelude::*;
wasm_bindgen! {
#[wasm_module = "./test"]
extern "JS" {
fn foo(s: &str);
fn another(a: u32) -> i32;
fn take_and_return_bool(a: bool) -> bool;
fn return_object() -> JsValue;
}
pub fn bar(s: &str) {
foo(s);
}
pub fn another_thunk(a: u32) -> i32 {
another(a)
}
pub fn bool_thunk(a: bool) -> bool {
take_and_return_bool(a)
}
#[wasm_bindgen(module = "./test")]
extern {
fn foo(s: &str);
fn another(a: u32) -> i32;
fn take_and_return_bool(a: bool) -> bool;
fn return_object() -> JsValue;
}
pub fn get_the_object() -> JsValue {
return_object()
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn bar(s: &str) {
foo(s);
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn another_thunk(a: u32) -> i32 {
another(a)
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn bool_thunk(a: bool) -> bool {
take_and_return_bool(a)
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn get_the_object() -> JsValue {
return_object()
}
"#)
.file("test.ts", r#"
@ -87,14 +96,14 @@ fn unused() {
use wasm_bindgen::prelude::*;
wasm_bindgen! {
#[wasm_module = "./test"]
extern "JS" {
fn debug_print(s: &str);
}
pub fn bar() {}
#[wasm_bindgen(module = "./test")]
extern {
fn debug_print(s: &str);
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn bar() {}
"#)
.file("test.ts", r#"
import * as wasm from "./out";
@ -118,19 +127,21 @@ fn strings() {
use wasm_bindgen::prelude::*;
wasm_bindgen! {
#[wasm_module = "./test"]
extern "JS" {
fn foo(a: String) -> String;
}
#[wasm_bindgen(module = "./test")]
extern {
fn foo(a: String) -> String;
}
pub fn bar(a: &str) -> String {
foo(a.to_string())
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn bar(a: &str) -> String {
foo(a.to_string())
}
pub fn bar2(a: String) -> String {
foo(a)
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn bar2(a: String) -> String {
foo(a)
}
"#)
.file("test.ts", r#"
@ -159,24 +170,26 @@ fn exceptions() {
use wasm_bindgen::prelude::*;
wasm_bindgen! {
#[wasm_module = "./test"]
extern "JS" {
fn foo();
fn bar();
#[wasm_bindgen(catch)]
fn baz() -> Result<(), JsValue>;
}
#[wasm_bindgen(module = "./test")]
extern {
fn foo();
fn bar();
#[wasm_bindgen(catch)]
fn baz() -> Result<(), JsValue>;
}
pub fn run() {
foo();
bar();
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn run() {
foo();
bar();
}
pub fn run2() {
assert!(baz().is_err());
bar();
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn run2() {
assert!(baz().is_err());
bar();
}
"#)
.file("test.ts", r#"
@ -217,16 +230,16 @@ fn exn_caught() {
use wasm_bindgen::prelude::*;
wasm_bindgen! {
#[wasm_module = "./test"]
extern "JS" {
#[wasm_bindgen(catch)]
fn foo() -> Result<(), JsValue>;
}
#[wasm_bindgen(module = "./test")]
extern {
#[wasm_bindgen(catch)]
fn foo() -> Result<(), JsValue>;
}
pub fn run() -> JsValue {
foo().unwrap_err()
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn run() -> JsValue {
foo().unwrap_err()
}
"#)
.file("test.ts", r#"
@ -245,3 +258,34 @@ fn exn_caught() {
"#)
.test();
}
#[test]
fn free_imports() {
test_support::project()
.file("src/lib.rs", r#"
#![feature(proc_macro)]
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern {
fn parseInt(a: &str) -> u32;
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn run() {
assert_eq!(parseInt("3"), 3);
}
"#)
.file("test.ts", r#"
import { run } from "./out";
export function test() {
run();
}
"#)
.test();
}

View File

@ -10,14 +10,15 @@ fn simple() {
use wasm_bindgen::prelude::*;
wasm_bindgen! {
#[wasm_module = "./test"]
extern "JS" {
fn foo(s: &JsValue);
}
pub fn bar(s: &JsValue) {
foo(s);
}
#[wasm_bindgen(module = "./test")]
extern {
fn foo(s: &JsValue);
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn bar(s: &JsValue) {
foo(s);
}
"#)
.file("test.ts", r#"
@ -51,14 +52,15 @@ fn owned() {
use wasm_bindgen::prelude::*;
wasm_bindgen! {
#[wasm_module = "./test"]
extern "JS" {
fn foo(s: JsValue);
}
pub fn bar(s: JsValue) {
foo(s);
}
#[wasm_bindgen(module = "./test")]
extern {
fn foo(s: JsValue);
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn bar(s: JsValue) {
foo(s);
}
"#)
.file("test.ts", r#"
@ -92,23 +94,23 @@ fn clone() {
use wasm_bindgen::prelude::*;
wasm_bindgen! {
#[wasm_module = "./test"]
extern "JS" {
fn foo1(s: JsValue);
fn foo2(s: &JsValue);
fn foo3(s: JsValue);
fn foo4(s: &JsValue);
fn foo5(s: JsValue);
}
#[wasm_bindgen(module = "./test")]
extern {
fn foo1(s: JsValue);
fn foo2(s: &JsValue);
fn foo3(s: JsValue);
fn foo4(s: &JsValue);
fn foo5(s: JsValue);
}
pub fn bar(s: JsValue) {
foo1(s.clone());
foo2(&s);
foo3(s.clone());
foo4(&s);
foo5(s);
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn bar(s: JsValue) {
foo1(s.clone());
foo2(&s);
foo3(s.clone());
foo4(&s);
foo5(s);
}
"#)
.file("test.ts", r#"
@ -140,21 +142,21 @@ fn promote() {
use wasm_bindgen::prelude::*;
wasm_bindgen! {
#[wasm_module = "./test"]
extern "JS" {
fn foo1(s: &JsValue);
fn foo2(s: JsValue);
fn foo3(s: &JsValue);
fn foo4(s: JsValue);
}
#[wasm_bindgen(module = "./test")]
extern {
fn foo1(s: &JsValue);
fn foo2(s: JsValue);
fn foo3(s: &JsValue);
fn foo4(s: JsValue);
}
pub fn bar(s: &JsValue) {
foo1(s);
foo2(s.clone());
foo3(s);
foo4(s.clone());
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn bar(s: &JsValue) {
foo1(s);
foo2(s.clone());
foo3(s);
foo4(s.clone());
}
"#)
.file("test.ts", r#"

View File

@ -11,19 +11,22 @@ fn works() {
use wasm_bindgen::prelude::*;
wasm_bindgen! {
pub struct A {}
#[wasm_bindgen]
pub struct A {}
impl A {
pub fn new() -> A {
A {}
}
}
pub fn clone(a: &JsValue) -> JsValue {
drop(a.clone());
a.clone()
#[wasm_bindgen]
impl A {
pub fn new() -> A {
A {}
}
}
#[wasm_bindgen]
#[no_mangle]
pub extern fn clone(a: &JsValue) -> JsValue {
drop(a.clone());
a.clone()
}
"#)
.file("test.ts", r#"
import * as assert from "assert";

View File

@ -10,28 +10,36 @@ fn add() {
use wasm_bindgen::prelude::*;
wasm_bindgen! {
pub fn add(a: u32, b: u32) -> u32 {
a + b
}
#[no_mangle]
#[wasm_bindgen]
pub extern fn add(a: u32, b: u32) -> u32 {
a + b
}
pub fn add3(a: u32) -> u32 {
a + 3
}
#[no_mangle]
#[wasm_bindgen]
pub extern fn add3(a: u32) -> u32 {
a + 3
}
pub fn get2(_b: bool) -> u32 {
2
}
#[no_mangle]
#[wasm_bindgen]
pub extern fn get2(_b: bool) -> u32 {
2
}
pub fn return_and_take_bool(a: bool, b: bool) -> bool {
a && b
}
#[no_mangle]
#[wasm_bindgen]
pub extern fn return_and_take_bool(a: bool, b: bool) -> bool {
a && b
}
pub fn raw_pointers_work(a: *mut u32, b: *const u8) -> *const u32 {
unsafe {
(*a) = (*b) as u32;
return a
}
#[no_mangle]
#[wasm_bindgen]
pub extern fn raw_pointers_work(a: *mut u32, b: *const u8) -> *const u32 {
unsafe {
(*a) = (*b) as u32;
return a
}
}
"#)
@ -60,15 +68,17 @@ fn string_arguments() {
use wasm_bindgen::prelude::*;
wasm_bindgen! {
pub fn assert_foo_and_bar(a: &str, b: &str) {
assert_eq!(a, "foo2");
assert_eq!(b, "bar");
}
#[no_mangle]
#[wasm_bindgen]
pub extern fn assert_foo_and_bar(a: &str, b: &str) {
assert_eq!(a, "foo2");
assert_eq!(b, "bar");
}
pub fn assert_foo(a: &str) {
assert_eq!(a, "foo");
}
#[no_mangle]
#[wasm_bindgen]
pub extern fn assert_foo(a: &str) {
assert_eq!(a, "foo");
}
"#)
.file("test.ts", r#"
@ -92,16 +102,16 @@ fn return_a_string() {
use wasm_bindgen::prelude::*;
wasm_bindgen! {
pub fn clone(a: &str) -> String {
a.to_string()
}
#[no_mangle]
#[wasm_bindgen]
pub extern fn clone(a: &str) -> String {
a.to_string()
}
wasm_bindgen! {
pub fn concat(a: &str, b: &str, c: i8) -> String {
format!("{} {} {}", a, b, c)
}
#[no_mangle]
#[wasm_bindgen]
pub extern fn concat(a: &str, b: &str, c: i8) -> String {
format!("{} {} {}", a, b, c)
}
"#)
.file("test.ts", r#"
@ -128,10 +138,13 @@ fn exceptions() {
use wasm_bindgen::prelude::*;
wasm_bindgen! {
pub fn foo(_a: u32) {}
pub fn bar(_a: &str) {}
}
#[no_mangle]
#[wasm_bindgen]
pub extern fn foo(_a: u32) {}
#[no_mangle]
#[wasm_bindgen]
pub extern fn bar(_a: &str) {}
"#)
.file("test.js", r#"
import * as assert from "assert";