Implement static imports

This allows importing static objects like `document`, `window`, or an arbitrary
JS object from a module
This commit is contained in:
Alex Crichton 2018-03-21 08:09:59 -07:00
parent eebe23649a
commit 8e894fcfc5
7 changed files with 189 additions and 3 deletions

View File

@ -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);

View File

@ -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(),
}
}
}

View File

@ -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);
}
}

View File

@ -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"),
}),
])
}
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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();
}