diff --git a/crates/wasm-bindgen-cli-support/src/js.rs b/crates/wasm-bindgen-cli-support/src/js.rs index a772f82e..c95d1be9 100644 --- a/crates/wasm-bindgen-cli-support/src/js.rs +++ b/crates/wasm-bindgen-cli-support/src/js.rs @@ -1044,9 +1044,12 @@ impl<'a, 'b> SubContext<'a, 'b> { for f in self.program.exports.iter() { self.generate_export(f); } - for f in self.program.imports.iter() { + for f in self.program.imported_functions.iter() { self.generate_import(f); } + for f in self.program.imported_fields.iter() { + self.generate_import_field(f); + } for e in self.program.enums.iter() { self.generate_enum(e); } @@ -1319,6 +1322,24 @@ impl<'a, 'b> SubContext<'a, 'b> { (format!("{} {}", prefix, dst), format!("{} {}", prefix, dst_ts)) } + pub fn generate_import_field(&mut self, import: &shared::ImportField) { + let name = import.shim_name(); + self.cx.imports_to_rewrite.insert(name.clone()); + + if let Some(ref module) = import.module { + self.cx.imports.push_str(&format!(" + import {{ {} }} from '{}'; + ", import.name, module)); + } + + self.cx.expose_add_heap_object(); + self.cx.globals.push_str(&format!(" + export function {}() {{ + return addHeapObject({}); + }} + ", name, import.name)); + } + pub fn generate_import(&mut self, import: &shared::Import) { if let Some(ref module) = import.module { let name_to_import = import.class.as_ref().unwrap_or(&import.function.name); diff --git a/crates/wasm-bindgen-macro/src/ast.rs b/crates/wasm-bindgen-macro/src/ast.rs index 11089b97..c4fc5cf8 100644 --- a/crates/wasm-bindgen-macro/src/ast.rs +++ b/crates/wasm-bindgen-macro/src/ast.rs @@ -11,6 +11,7 @@ pub struct Program { pub enums: Vec<Enum>, pub imported_types: Vec<ImportedType>, pub structs: Vec<Struct>, + pub imported_fields: Vec<ImportField>, } pub struct Export { @@ -62,6 +63,13 @@ pub struct ImportedType { pub name: syn::Ident, } +pub struct ImportField { + pub vis: syn::Visibility, + pub ty: syn::Type, + pub module: Option<String>, + pub name: syn::Ident, +} + pub enum Type { // special Vector(VectorType, bool), @@ -208,6 +216,7 @@ impl Program { match item { syn::ForeignItem::Fn(f) => self.push_foreign_fn(f, &opts), syn::ForeignItem::Type(t) => self.push_foreign_ty(t, &opts), + syn::ForeignItem::Static(s) => self.push_foreign_static(s, &opts), _ => panic!("only foreign functions/types allowed for now"), } } @@ -341,6 +350,20 @@ impl Program { }); } + pub fn push_foreign_static(&mut self, + f: syn::ForeignItemStatic, + module_opts: &BindgenAttrs) { + if f.mutability.is_some() { + panic!("cannot import mutable globals yet") + } + self.imported_fields.push(ImportField { + module: module_opts.module().map(|s| s.to_string()), + ty: *f.ty, + vis: f.vis, + name: f.ident + }); + } + pub fn literal(&self, dst: &mut Tokens) -> usize { let mut tmp = Tokens::new(); let cnt = { @@ -792,3 +815,12 @@ impl ToTokens for VectorType { me.to_tokens(tokens); } } + +impl ImportField { + pub fn shared(&self) -> shared::ImportField { + shared::ImportField { + module: self.module.clone(), + name: self.name.to_string(), + } + } +} diff --git a/crates/wasm-bindgen-macro/src/lib.rs b/crates/wasm-bindgen-macro/src/lib.rs index e65dbd35..c48b7df3 100755 --- a/crates/wasm-bindgen-macro/src/lib.rs +++ b/crates/wasm-bindgen-macro/src/lib.rs @@ -78,6 +78,9 @@ impl ToTokens for ast::Program { for it in self.imported_types.iter() { it.to_tokens(tokens); } + for it in self.imported_fields.iter() { + it.to_tokens(tokens); + } // 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 @@ -591,3 +594,28 @@ impl ToTokens for ast::Enum { }).to_tokens(into); } } + +impl ToTokens for ast::ImportField { + fn to_tokens(&self, into: &mut Tokens) { + let name = self.name; + let ty = &self.ty; + let shim_name = syn::Ident::from(self.shared().shim_name()); + let vis = &self.vis; + (my_quote! { + #vis static #name: ::wasm_bindgen::JsStatic<#ty> = { + fn init() -> #ty { + extern { + fn #shim_name() -> u32; + } + unsafe { + ::wasm_bindgen::convert::WasmBoundary::from_js(#shim_name()) + } + } + ::wasm_bindgen::JsStatic { + __inner: ::std::cell::UnsafeCell::new(None), + __init: init, + } + }; + }).to_tokens(into); + } +} diff --git a/crates/wasm-bindgen-macro/src/literal.rs b/crates/wasm-bindgen-macro/src/literal.rs index 4f3f6a0e..8e682009 100644 --- a/crates/wasm-bindgen-macro/src/literal.rs +++ b/crates/wasm-bindgen-macro/src/literal.rs @@ -107,7 +107,8 @@ impl Literal for ast::Program { fn literal(&self, a: &mut LiteralBuilder) { a.fields(&[ ("exports", &|a| a.list_of(&self.exports)), - ("imports", &|a| a.list_of(&self.imports)), + ("imported_functions", &|a| a.list_of(&self.imports)), + ("imported_fields", &|a| a.list_of(&self.imported_fields)), ("enums", &|a| a.list_of(&self.enums)), ("custom_type_names", &|a| { let names = self.exports @@ -271,3 +272,15 @@ impl Literal for ast::Variant { ]) } } + +impl Literal for ast::ImportField { + fn literal(&self, a: &mut LiteralBuilder) { + a.fields(&[ + ("name", &|a| a.str(self.name.as_ref())), + ("module", &|a| match self.module { + Some(ref s) => a.str(s), + None => a.append("null"), + }), + ]) + } +} diff --git a/crates/wasm-bindgen-shared/src/lib.rs b/crates/wasm-bindgen-shared/src/lib.rs index 89d57375..b3ec75b8 100644 --- a/crates/wasm-bindgen-shared/src/lib.rs +++ b/crates/wasm-bindgen-shared/src/lib.rs @@ -11,7 +11,8 @@ pub const SCHEMA_VERSION: &str = "0"; pub struct Program { pub exports: Vec<Export>, pub enums: Vec<Enum>, - pub imports: Vec<Import>, + pub imported_functions: Vec<Import>, + pub imported_fields: Vec<ImportField>, pub custom_type_names: Vec<CustomTypeName>, pub version: String, pub schema_version: String, @@ -30,6 +31,12 @@ pub struct Import { pub function: Function, } +#[derive(Deserialize)] +pub struct ImportField { + pub module: Option<String>, + pub name: String, +} + #[derive(Deserialize)] pub struct Export { pub class: Option<String>, @@ -148,3 +155,9 @@ pub fn version() -> String { } return v } + +impl ImportField { + pub fn shim_name(&self) -> String { + format!("__wbg_field_import_shim_{}", self.name) + } +} diff --git a/src/lib.rs b/src/lib.rs index 9cef30bf..deb1e076 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,8 +9,12 @@ extern crate wasm_bindgen_macro; +use std::cell::UnsafeCell; +use std::ops::Deref; use std::ptr; +use convert::WasmBoundary; + /// A module which is typically glob imported from: /// /// ``` @@ -243,6 +247,49 @@ impl Drop for JsValue { } } +/// Wrapper type for imported statics. +/// +/// This type is used whenever a `static` is imported from a JS module, for +/// example this import: +/// +/// ```ignore +/// #[wasm_bindgen] +/// extern { +/// static console: JsValue; +/// } +/// ``` +/// +/// will generate in Rust a value that looks like: +/// +/// ```ignore +/// static console: JsStatic<JsValue> = ...; +/// ``` +/// +/// This type implements `Deref` to the inner type so it's typically used as if +/// it were `&T`. +pub struct JsStatic<T> { + #[doc(hidden)] + pub __inner: UnsafeCell<Option<T>>, + #[doc(hidden)] + pub __init: fn() -> T, +} + +unsafe impl<T: Sync> Sync for JsStatic<T> {} +unsafe impl<T: Send> Send for JsStatic<T> {} + +impl<T: WasmBoundary> Deref for JsStatic<T> { + type Target = T; + fn deref(&self) -> &T { + unsafe { + (*self.__inner.get()).get_or_insert_with(|| { + assert!(T::DESCRIPTOR == JsValue::DESCRIPTOR, + "only JS values can be imported as statics for now"); + (self.__init)() + }) + } + } +} + /// Throws a JS exception. /// /// This function will throw a JS exception with the message provided. The diff --git a/tests/imports.rs b/tests/imports.rs index 0e8cef3a..6bcfd939 100644 --- a/tests/imports.rs +++ b/tests/imports.rs @@ -278,3 +278,35 @@ fn free_imports() { "#) .test(); } + +#[test] +fn import_a_field() { + test_support::project() + .file("src/lib.rs", r#" + #![feature(proc_macro)] + + extern crate wasm_bindgen; + + use wasm_bindgen::prelude::*; + + #[wasm_bindgen(module = "./test")] + extern { + static IMPORT: JsValue; + } + + #[wasm_bindgen] + pub fn run() { + assert_eq!(IMPORT.as_f64(), Some(1.0)); + } + "#) + .file("test.ts", r#" + import { run } from "./out"; + + export const IMPORT = 1.0; + + export function test() { + run(); + } + "#) + .test(); +}