Support imports outside of wasm_bindgen!

This commit is contained in:
Alex Crichton 2017-12-20 07:58:41 -08:00
parent 66ae545bff
commit a12a8f414c
2 changed files with 118 additions and 23 deletions

View File

@ -1,4 +1,4 @@
use std::collections::HashSet; use std::collections::{HashSet, HashMap};
use shared; use shared;
use parity_wasm::elements::*; use parity_wasm::elements::*;
@ -352,21 +352,55 @@ impl Js {
typescript_exports.push_str("\n"); typescript_exports.push_str("\n");
} }
exports.push_str("}"); exports.push_str("}");
let mut imports = String::new(); let wasm_imports = self.typescript_wasm_imports(m);
let mut typescript_imports = String::new();
let mut imports_object = String::new();
let mut extra_imports_interface = String::new();
let mut imports_bound = HashSet::new();
let mut imports_interface = String::new();
for &(ref import, ref val, ref ts_import) in self.imports.iter() { for &(ref import, ref val, ref ts_import) in self.imports.iter() {
imports.push_str(import); imports_bound.insert(import.clone());
imports.push_str(":"); imports_object.push_str(import);
imports.push_str(val); imports_object.push_str(":");
imports.push_str(",\n"); imports_object.push_str(val);
typescript_imports.push_str(ts_import); imports_object.push_str(",\n");
typescript_imports.push_str("\n"); imports_interface.push_str(ts_import);
imports_interface.push_str("\n");
}
// If the user otherwise specified functions to import which *weren't*
// part of wasm-bindgen we want to make sure they come through here as
// well.
for (import, typescript) in wasm_imports.iter() {
// ignore any internal functions we have for ourselves
if import.starts_with("__wbindgen") {
continue
}
// Ignore anything we just bound above,
if imports_bound.contains(import) {
continue
}
if extra_imports_interface.len() == 0 {
extra_imports_interface.push_str("interface ExtraImports {\n");
imports_interface.push_str("env: ExtraImports;\n");
}
imports_object.push_str(import);
imports_object.push_str(":");
imports_object.push_str("imports.env.");
imports_object.push_str(import);
imports_object.push_str(",\n");
extra_imports_interface.push_str(typescript);
extra_imports_interface.push_str("\n");
}
if extra_imports_interface.len() > 0 {
extra_imports_interface.push_str("}\n");
} }
if self.wasm_import_needed("__wbindgen_object_clone_ref", m) { if self.wasm_import_needed("__wbindgen_object_clone_ref", m) {
self.expose_add_heap_object(); self.expose_add_heap_object();
self.expose_get_object(); self.expose_get_object();
imports.push_str(" imports_object.push_str("
__wbindgen_object_clone_ref: function(idx: number): number { __wbindgen_object_clone_ref: function(idx: number): number {
// If this object is on the stack promote it to the heap. // If this object is on the stack promote it to the heap.
if ((idx & 1) === 1) { if ((idx & 1) === 1) {
@ -386,12 +420,12 @@ impl Js {
if self.wasm_import_needed("__wbindgen_object_drop_ref", m) { if self.wasm_import_needed("__wbindgen_object_drop_ref", m) {
self.expose_drop_ref(); self.expose_drop_ref();
imports.push_str("__wbindgen_object_drop_ref: dropRef,\n"); imports_object.push_str("__wbindgen_object_drop_ref: dropRef,\n");
} }
if self.wasm_import_needed("__wbindgen_throw", m) { if self.wasm_import_needed("__wbindgen_throw", m) {
self.expose_get_string_from_wasm(); self.expose_get_string_from_wasm();
imports.push_str("__wbindgen_throw: function(ptr: number, len: number) { imports_object.push_str("__wbindgen_throw: function(ptr: number, len: number) {
throw new Error(getStringFromWasm(ptr, len)); throw new Error(getStringFromWasm(ptr, len));
},\n"); },\n");
} }
@ -419,9 +453,11 @@ impl Js {
}} }}
export interface Imports {{ export interface Imports {{
{typescript_imports} {imports_interface}
}} }}
{extra_imports_interface}
export interface Exports {{ export interface Exports {{
module: WebAssembly.Module; module: WebAssembly.Module;
instance: WebAssembly.Module; instance: WebAssembly.Module;
@ -437,7 +473,7 @@ impl Js {
export function instantiate(bytes: any, imports: Imports): Promise<Exports> {{ export function instantiate(bytes: any, imports: Imports): Promise<Exports> {{
let wasm_imports: WasmImportsTop = {{ let wasm_imports: WasmImportsTop = {{
env: {{ env: {{
{imports} {imports_object}
}}, }},
}}; }};
return WebAssembly.instantiate(bytes, wasm_imports).then(xform); return WebAssembly.instantiate(bytes, wasm_imports).then(xform);
@ -445,11 +481,15 @@ impl Js {
", ",
globals = self.globals, globals = self.globals,
exports = exports, exports = exports,
imports = imports, imports_object = imports_object,
writes = writes, writes = writes,
typescript_imports = typescript_imports, extra_imports_interface = extra_imports_interface,
imports_interface = imports_interface,
typescript_exports = typescript_exports, typescript_exports = typescript_exports,
wasm_imports = self.typescript_wasm_imports(m), wasm_imports = wasm_imports.values()
.map(|s| &**s)
.collect::<Vec<_>>()
.join("\n"),
wasm_exports = self.typescript_wasm_exports(m), wasm_exports = self.typescript_wasm_exports(m),
) )
} }
@ -465,17 +505,21 @@ impl Js {
}) })
} }
fn typescript_wasm_imports(&self, m: &Module) -> String { /// Returns a map of import name to the typescript definition for that name.
///
/// This function generates the list of imports that a wasm module has,
/// using the source of truth (the was module itself) to generate this list.
fn typescript_wasm_imports(&self, m: &Module) -> HashMap<String, String> {
let imports = match m.import_section() { let imports = match m.import_section() {
Some(s) => s, Some(s) => s,
None => return String::new(), None => return HashMap::new(),
}; };
let types = match m.type_section() { let types = match m.type_section() {
Some(s) => s, Some(s) => s,
None => return String::new(), None => return HashMap::new(),
}; };
let mut ts = String::new(); let mut map = HashMap::new();
for import in imports.entries() { for import in imports.entries() {
assert_eq!(import.module(), "env"); assert_eq!(import.module(), "env");
@ -488,6 +532,7 @@ impl Js {
_ => continue, _ => continue,
}; };
let mut ts = String::new();
ts.push_str(import.field()); ts.push_str(import.field());
ts.push_str("("); ts.push_str("(");
// TODO: probably match `arg` to catch exhaustive errors in the // TODO: probably match `arg` to catch exhaustive errors in the
@ -504,11 +549,18 @@ impl Js {
} else { } else {
ts.push_str("number"); ts.push_str("number");
} }
ts.push_str(";\n"); ts.push_str(";");
map.insert(import.field().to_string(), ts);
} }
return ts; return map;
} }
/// Returns a block describing all the raw wasm exports that this module
/// has.
///
/// This uses the module itself as the source of truth to help flesh out
/// bugs in this program.
fn typescript_wasm_exports(&self, m: &Module) -> String { fn typescript_wasm_exports(&self, m: &Module) -> String {
let imported_functions = match m.import_section() { let imported_functions = match m.import_section() {
Some(s) => s.functions(), Some(s) => s.functions(),

View File

@ -152,3 +152,46 @@ fn exceptions() {
"#) "#)
.test(); .test();
} }
#[test]
fn other_imports() {
test_support::project()
.file("src/lib.rs", r#"
#![feature(proc_macro)]
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
extern {
fn another_import(a: u32);
}
wasm_bindgen! {
pub fn foo(a: u32) {
unsafe { another_import(a); }
}
}
"#)
.file("test.ts", r#"
import * as assert from "assert";
import { Exports, Imports } from "./out";
let ARG: number | null = null;
export const imports: Imports = {
env: {
another_import(a: number) {
assert.strictEqual(ARG, null);
ARG = a;
},
},
};
export function test(wasm: Exports) {
wasm.foo(2);
assert.strictEqual(ARG, 2);
}
"#)
.test();
}