1
0
mirror of https://github.com/fluencelabs/wasm-bindgen synced 2025-03-23 13:40:52 +00:00

958 lines
28 KiB
Rust
Raw Normal View History

use quote::{ToTokens, Tokens};
use shared;
2017-12-14 19:31:01 -08:00
use syn;
#[derive(Default)]
2017-12-18 12:39:14 -08:00
pub struct Program {
pub exports: Vec<Export>,
pub imports: Vec<Import>,
2018-02-22 00:55:11 +01:00
pub enums: Vec<Enum>,
pub structs: Vec<Struct>,
2017-12-18 12:39:14 -08:00
}
pub struct Export {
pub class: Option<syn::Ident>,
pub method: bool,
pub mutable: bool,
2018-04-14 16:41:41 +02:00
pub constructor: Option<String>,
pub function: Function,
2017-12-14 19:31:01 -08:00
}
pub struct Import {
pub module: Option<String>,
pub js_namespace: Option<syn::Ident>,
pub kind: ImportKind,
2018-02-05 14:24:25 -08:00
}
pub enum ImportKind {
Function(ImportFunction),
Static(ImportStatic),
Type(ImportType),
}
pub struct ImportFunction {
pub function: Function,
pub rust_name: syn::Ident,
pub kind: ImportFunctionKind,
pub shim: syn::Ident,
}
pub enum ImportFunctionKind {
Method { class: String, ty: syn::Type },
JsConstructor { class: String, ty: syn::Type },
Normal,
2018-02-05 14:24:25 -08:00
}
pub struct ImportStatic {
pub vis: syn::Visibility,
pub ty: syn::Type,
pub shim: syn::Ident,
pub rust_name: syn::Ident,
pub js_name: syn::Ident,
}
pub struct ImportType {
pub vis: syn::Visibility,
pub name: syn::Ident,
}
pub struct Function {
2018-02-05 14:24:25 -08:00
pub name: syn::Ident,
pub arguments: Vec<syn::Type>,
pub ret: Option<syn::Type>,
pub opts: BindgenAttrs,
pub rust_attrs: Vec<syn::Attribute>,
pub rust_decl: Box<syn::FnDecl>,
pub rust_vis: syn::Visibility,
}
pub struct Struct {
pub name: syn::Ident,
pub fields: Vec<StructField>,
}
pub struct StructField {
pub name: syn::Ident,
pub struct_name: syn::Ident,
pub ty: syn::Type,
pub getter: syn::Ident,
pub setter: syn::Ident,
}
2018-02-22 00:55:11 +01:00
pub struct Enum {
pub name: syn::Ident,
pub variants: Vec<Variant>,
}
pub struct Variant {
pub name: syn::Ident,
pub value: u32,
2018-02-22 00:55:11 +01:00
}
#[derive(Copy, Clone)]
pub enum TypeKind {
ByRef,
ByMutRef,
ByValue,
}
#[derive(Copy, Clone)]
pub enum TypeLocation {
ImportArgument,
ImportRet,
ExportArgument,
ExportRet,
2017-12-18 12:39:14 -08:00
}
impl Program {
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);
}
_ => {}
}
f.to_tokens(tokens);
self.exports.push(Export {
class: None,
method: false,
mutable: false,
2018-04-14 16:41:41 +02:00
constructor: None,
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));
self.push_impl(&mut i, opts);
i.to_tokens(tokens);
}
syn::Item::ForeignMod(mut f) => {
let opts = opts.unwrap_or_else(|| BindgenAttrs::find(&mut f.attrs));
self.push_foreign_mod(f, opts);
}
2018-02-22 00:55:11 +01:00
syn::Item::Enum(mut e) => {
let opts = opts.unwrap_or_else(|| BindgenAttrs::find(&mut e.attrs));
e.to_tokens(tokens);
self.push_enum(e, opts);
}
_ => panic!(
"#[wasm_bindgen] can only be applied to a function, \
struct, enum, impl, or extern block"
),
}
}
pub fn push_impl(&mut self, item: &mut syn::ItemImpl, _opts: BindgenAttrs) {
2017-12-31 14:40:57 -08:00
if item.defaultness.is_some() {
panic!("default impls are not supported");
2017-12-18 12:39:14 -08:00
}
2017-12-31 14:40:57 -08:00
if item.unsafety.is_some() {
panic!("unsafe impls are not supported");
2017-12-18 12:39:14 -08:00
}
if item.trait_.is_some() {
panic!("trait impls are not supported");
}
if item.generics.params.len() > 0 {
panic!("generic impls aren't supported");
}
let name = match *item.self_ty {
syn::Type::Path(syn::TypePath {
qself: None,
ref path,
}) => match extract_path_ident(path) {
Some(ident) => ident,
None => panic!("unsupported self type in impl"),
},
2017-12-18 12:39:14 -08:00
_ => panic!("unsupported self type in impl"),
};
for item in item.items.iter_mut() {
self.push_impl_item(name, item);
2017-12-18 12:39:14 -08:00
}
}
fn push_impl_item(&mut self, class: syn::Ident, item: &mut syn::ImplItem) {
replace_self(class, item);
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 mut 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);
2018-04-14 16:41:41 +02:00
let is_constructor = opts.constructor();
let constructor = if is_constructor {
Some(method.sig.ident.to_string())
} else {
None
};
let (function, mutable) = Function::from_decl(
method.sig.ident,
Box::new(method.sig.decl.clone()),
method.attrs.clone(),
opts,
method.vis.clone(),
true,
);
self.exports.push(Export {
class: Some(class),
method: mutable.is_some(),
mutable: mutable.unwrap_or(false),
constructor,
function,
});
}
2018-02-22 12:08:28 +01:00
pub fn push_enum(&mut self, item: syn::ItemEnum, _opts: BindgenAttrs) {
2018-02-22 00:55:11 +01:00
match item.vis {
syn::Visibility::Public(_) => {}
_ => panic!("only public enums are allowed"),
}
let variants = item.variants
.iter()
.enumerate()
.map(|(i, v)| {
match v.fields {
syn::Fields::Unit => (),
_ => panic!("Only C-Style enums allowed"),
}
let value = match v.discriminant {
Some((
_,
syn::Expr::Lit(syn::ExprLit {
attrs: _,
lit: syn::Lit::Int(ref int_lit),
}),
)) => {
if int_lit.value() > <u32>::max_value() as u64 {
panic!("Enums can only support numbers that can be represented as u32");
}
int_lit.value() as u32
}
None => i as u32,
_ => panic!("Enums may only have number literal values"),
};
Variant {
name: v.ident,
value,
}
})
.collect();
self.enums.push(Enum {
name: item.ident,
variants,
2018-02-22 00:55:11 +01:00
});
}
pub fn push_foreign_mod(&mut self, f: syn::ItemForeignMod, opts: BindgenAttrs) {
match f.abi.name {
Some(ref l) if l.value() == "C" => {}
None => {}
_ => panic!("only foreign mods with the `C` ABI are allowed"),
}
for mut item in f.items.into_iter() {
let item_opts = {
let attrs = match item {
syn::ForeignItem::Fn(ref mut f) => &mut f.attrs,
syn::ForeignItem::Type(ref mut t) => &mut t.attrs,
syn::ForeignItem::Static(ref mut s) => &mut s.attrs,
_ => panic!("only foreign functions/types allowed for now"),
};
BindgenAttrs::find(attrs)
};
let module = item_opts.module().or(opts.module()).map(|s| s.to_string());
let js_namespace = item_opts.js_namespace().or(opts.js_namespace());
let mut kind = match item {
syn::ForeignItem::Fn(f) => self.push_foreign_fn(f, item_opts),
syn::ForeignItem::Type(t) => self.push_foreign_ty(t),
syn::ForeignItem::Static(s) => self.push_foreign_static(s, item_opts),
_ => panic!("only foreign functions/types allowed for now"),
};
self.imports.push(Import {
module,
js_namespace,
kind,
});
}
}
pub fn push_foreign_fn(&mut self, f: syn::ForeignItemFn, opts: BindgenAttrs) -> ImportKind {
let js_name = opts.js_name().unwrap_or(f.ident);
let mut wasm = Function::from_decl(
js_name,
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`
// * The error type is a `JsValue`
// * The actual type is the first type parameter
//
// should probably fix this one day...
wasm.ret = extract_first_ty_param(wasm.ret.as_ref())
.expect("can't `catch` without returning a Result");
}
2018-02-05 14:24:25 -08:00
let kind = if wasm.opts.method() {
let class = wasm.arguments
.get(0)
.expect("methods must have at least one argument");
let class = match *class {
syn::Type::Reference(syn::TypeReference {
mutability: None,
ref elem,
..
}) => &**elem,
_ => panic!("first argument of method must be a shared reference"),
};
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");
ImportFunctionKind::Method {
class: class_name.as_ref().to_string(),
ty: class.clone(),
}
} else if wasm.opts.constructor() {
let class = match wasm.ret {
Some(ref ty) => ty,
_ => 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");
ImportFunctionKind::JsConstructor {
class: class_name.as_ref().to_string(),
ty: class.clone(),
}
} else {
ImportFunctionKind::Normal
};
let shim = {
let ns = match kind {
ImportFunctionKind::Normal => "n",
ImportFunctionKind::Method { ref class, .. } => class,
ImportFunctionKind::JsConstructor { ref class, .. } => class,
};
format!("__wbg_f_{}_{}_{}", js_name, f.ident, ns)
};
ImportKind::Function(ImportFunction {
function: wasm,
kind,
rust_name: f.ident,
shim: shim.into(),
})
}
pub fn push_foreign_ty(&mut self, f: syn::ForeignItemType) -> ImportKind {
ImportKind::Type(ImportType {
vis: f.vis,
name: f.ident,
})
}
pub fn push_foreign_static(
&mut self,
f: syn::ForeignItemStatic,
opts: BindgenAttrs,
) -> ImportKind {
if f.mutability.is_some() {
panic!("cannot import mutable globals yet")
}
let js_name = opts.js_name().unwrap_or(f.ident);
let shim = format!("__wbg_static_accessor_{}_{}", js_name, f.ident);
ImportKind::Static(ImportStatic {
ty: *f.ty,
vis: f.vis,
rust_name: f.ident,
js_name,
shim: shim.into(),
})
}
Overhaul how type information gets to the CLI This commit is a complete overhaul of how the `#[wasm_bindgen]` macro communicates type information to the CLI tool, and it's done in a somewhat... unconventional fashion. Today we've got a problem where the generated JS needs to understand the types of each function exported or imported. This understanding is what enables it to generate the appropriate JS wrappers and such. We want to, however, be quite flexible and extensible in types that are supported across the boundary, which means that internally we rely on the trait system to resolve what's what. Communicating the type information historically was done by creating a four byte "descriptor" and using associated type projections to communicate that to the CLI tool. Unfortunately four bytes isn't a lot of space to cram information like arguments to a generic function, tuple types, etc. In general this just wasn't flexible enough and the way custom references were treated was also already a bit of a hack. This commit takes a radical step of creating a **descriptor function** for each function imported/exported. The really crazy part is that the `wasm-bindgen` CLI tool now embeds a wasm interpreter and executes these functions when the CLI tool is invoked. By allowing arbitrary functions to get executed it's now *much* easier to inform `wasm-bindgen` about complicated structures of types. Rest assured though that all these descriptor functions are automatically unexported and gc'd away, so this should not have any impact on binary sizes A new internal trait, `WasmDescribe`, is added to represent a description of all types, sort of like a serialization of the structure of a type that `wasm-bindgen` can understand. This works by calling a special exported function with a `u32` value a bunch of times. This means that when we run a descriptor we effectively get a `Vec<u32>` in the `wasm-bindgen` CLI tool. This list of integers can then be parsed into a rich `enum` for the JS generation to work with. This commit currently only retains feature parity with the previous implementation. I hope to soon solve issues like #123, #104, and #111 with this support.
2018-04-13 07:33:46 -07:00
pub fn shared(&self) -> shared::Program {
shared::Program {
exports: self.exports.iter().map(|a| a.shared()).collect(),
structs: self.structs.iter().map(|a| a.shared()).collect(),
Overhaul how type information gets to the CLI This commit is a complete overhaul of how the `#[wasm_bindgen]` macro communicates type information to the CLI tool, and it's done in a somewhat... unconventional fashion. Today we've got a problem where the generated JS needs to understand the types of each function exported or imported. This understanding is what enables it to generate the appropriate JS wrappers and such. We want to, however, be quite flexible and extensible in types that are supported across the boundary, which means that internally we rely on the trait system to resolve what's what. Communicating the type information historically was done by creating a four byte "descriptor" and using associated type projections to communicate that to the CLI tool. Unfortunately four bytes isn't a lot of space to cram information like arguments to a generic function, tuple types, etc. In general this just wasn't flexible enough and the way custom references were treated was also already a bit of a hack. This commit takes a radical step of creating a **descriptor function** for each function imported/exported. The really crazy part is that the `wasm-bindgen` CLI tool now embeds a wasm interpreter and executes these functions when the CLI tool is invoked. By allowing arbitrary functions to get executed it's now *much* easier to inform `wasm-bindgen` about complicated structures of types. Rest assured though that all these descriptor functions are automatically unexported and gc'd away, so this should not have any impact on binary sizes A new internal trait, `WasmDescribe`, is added to represent a description of all types, sort of like a serialization of the structure of a type that `wasm-bindgen` can understand. This works by calling a special exported function with a `u32` value a bunch of times. This means that when we run a descriptor we effectively get a `Vec<u32>` in the `wasm-bindgen` CLI tool. This list of integers can then be parsed into a rich `enum` for the JS generation to work with. This commit currently only retains feature parity with the previous implementation. I hope to soon solve issues like #123, #104, and #111 with this support.
2018-04-13 07:33:46 -07:00
enums: self.enums.iter().map(|a| a.shared()).collect(),
imports: self.imports.iter().map(|a| a.shared()).collect(),
version: shared::version(),
schema_version: shared::SCHEMA_VERSION.to_string(),
}
2017-12-18 12:39:14 -08:00
}
2017-12-14 19:31:01 -08:00
}
impl Function {
pub fn from(input: syn::ItemFn, opts: BindgenAttrs) -> Function {
2017-12-14 19:31:01 -08:00
match input.vis {
syn::Visibility::Public(_) => {}
_ => panic!("can only bindgen public functions"),
}
2017-12-31 14:40:57 -08:00
if input.constness.is_some() {
panic!("can only bindgen non-const functions");
2017-12-14 19:31:01 -08:00
}
2017-12-31 14:40:57 -08:00
if input.unsafety.is_some() {
panic!("can only bindgen safe functions");
2017-12-14 19:31:01 -08:00
}
2017-12-18 12:39:14 -08:00
Function::from_decl(
input.ident,
input.decl,
input.attrs,
opts,
input.vis,
false,
).0
}
pub fn from_decl(
name: syn::Ident,
mut decl: Box<syn::FnDecl>,
attrs: Vec<syn::Attribute>,
opts: BindgenAttrs,
vis: syn::Visibility,
allow_self: bool,
) -> (Function, Option<bool>) {
2017-12-31 14:40:57 -08:00
if decl.variadic.is_some() {
2017-12-14 19:31:01 -08:00
panic!("can't bindgen variadic functions")
}
if decl.generics.params.len() > 0 {
2017-12-14 19:31:01 -08:00
panic!("can't bindgen functions with lifetime or type parameters")
}
assert_no_lifetimes(&mut decl);
2018-02-05 14:24:25 -08:00
let mut mutable = None;
let arguments = decl.inputs
.iter()
.filter_map(|arg| match *arg {
syn::FnArg::Captured(ref c) => Some(c),
syn::FnArg::SelfValue(_) => {
panic!("by-value `self` not yet supported");
}
syn::FnArg::SelfRef(ref a) if allow_self => {
assert!(mutable.is_none());
mutable = Some(a.mutability.is_some());
None
2017-12-14 19:31:01 -08:00
}
_ => panic!("arguments cannot be `self` or ignored"),
2017-12-14 19:31:01 -08:00
})
.map(|arg| arg.ty.clone())
2017-12-14 19:31:01 -08:00
.collect::<Vec<_>>();
let ret = match decl.output {
2017-12-14 19:31:01 -08:00
syn::ReturnType::Default => None,
syn::ReturnType::Type(_, ref t) => Some((**t).clone()),
2017-12-14 19:31:01 -08:00
};
(
Function {
name,
arguments,
ret,
opts,
rust_vis: vis,
rust_decl: decl,
rust_attrs: attrs,
},
mutable,
)
2017-12-14 19:31:01 -08:00
}
Overhaul how type information gets to the CLI This commit is a complete overhaul of how the `#[wasm_bindgen]` macro communicates type information to the CLI tool, and it's done in a somewhat... unconventional fashion. Today we've got a problem where the generated JS needs to understand the types of each function exported or imported. This understanding is what enables it to generate the appropriate JS wrappers and such. We want to, however, be quite flexible and extensible in types that are supported across the boundary, which means that internally we rely on the trait system to resolve what's what. Communicating the type information historically was done by creating a four byte "descriptor" and using associated type projections to communicate that to the CLI tool. Unfortunately four bytes isn't a lot of space to cram information like arguments to a generic function, tuple types, etc. In general this just wasn't flexible enough and the way custom references were treated was also already a bit of a hack. This commit takes a radical step of creating a **descriptor function** for each function imported/exported. The really crazy part is that the `wasm-bindgen` CLI tool now embeds a wasm interpreter and executes these functions when the CLI tool is invoked. By allowing arbitrary functions to get executed it's now *much* easier to inform `wasm-bindgen` about complicated structures of types. Rest assured though that all these descriptor functions are automatically unexported and gc'd away, so this should not have any impact on binary sizes A new internal trait, `WasmDescribe`, is added to represent a description of all types, sort of like a serialization of the structure of a type that `wasm-bindgen` can understand. This works by calling a special exported function with a `u32` value a bunch of times. This means that when we run a descriptor we effectively get a `Vec<u32>` in the `wasm-bindgen` CLI tool. This list of integers can then be parsed into a rich `enum` for the JS generation to work with. This commit currently only retains feature parity with the previous implementation. I hope to soon solve issues like #123, #104, and #111 with this support.
2018-04-13 07:33:46 -07:00
fn shared(&self) -> shared::Function {
shared::Function {
name: self.name.as_ref().to_string(),
}
}
2017-12-14 19:31:01 -08:00
}
pub fn extract_path_ident(path: &syn::Path) -> Option<syn::Ident> {
if path.leading_colon.is_some() {
return None;
}
if path.segments.len() != 1 {
return None;
}
2018-01-08 10:42:01 -08:00
match path.segments.first().unwrap().value().arguments {
syn::PathArguments::None => {}
_ => return None,
}
path.segments.first().map(|v| v.value().ident)
}
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());
2017-12-18 12:39:14 -08:00
}
generated_name.push_str("_");
generated_name.push_str(self.function.name.as_ref());
syn::Ident::from(generated_name)
2017-12-18 12:39:14 -08:00
}
Overhaul how type information gets to the CLI This commit is a complete overhaul of how the `#[wasm_bindgen]` macro communicates type information to the CLI tool, and it's done in a somewhat... unconventional fashion. Today we've got a problem where the generated JS needs to understand the types of each function exported or imported. This understanding is what enables it to generate the appropriate JS wrappers and such. We want to, however, be quite flexible and extensible in types that are supported across the boundary, which means that internally we rely on the trait system to resolve what's what. Communicating the type information historically was done by creating a four byte "descriptor" and using associated type projections to communicate that to the CLI tool. Unfortunately four bytes isn't a lot of space to cram information like arguments to a generic function, tuple types, etc. In general this just wasn't flexible enough and the way custom references were treated was also already a bit of a hack. This commit takes a radical step of creating a **descriptor function** for each function imported/exported. The really crazy part is that the `wasm-bindgen` CLI tool now embeds a wasm interpreter and executes these functions when the CLI tool is invoked. By allowing arbitrary functions to get executed it's now *much* easier to inform `wasm-bindgen` about complicated structures of types. Rest assured though that all these descriptor functions are automatically unexported and gc'd away, so this should not have any impact on binary sizes A new internal trait, `WasmDescribe`, is added to represent a description of all types, sort of like a serialization of the structure of a type that `wasm-bindgen` can understand. This works by calling a special exported function with a `u32` value a bunch of times. This means that when we run a descriptor we effectively get a `Vec<u32>` in the `wasm-bindgen` CLI tool. This list of integers can then be parsed into a rich `enum` for the JS generation to work with. This commit currently only retains feature parity with the previous implementation. I hope to soon solve issues like #123, #104, and #111 with this support.
2018-04-13 07:33:46 -07:00
pub fn export_name(&self) -> String {
match self.class {
Some(class) => {
shared::struct_function_export_name(class.as_ref(), self.function.name.as_ref())
2017-12-14 21:55:21 -08:00
}
None => shared::free_function_export_name(self.function.name.as_ref()),
Overhaul how type information gets to the CLI This commit is a complete overhaul of how the `#[wasm_bindgen]` macro communicates type information to the CLI tool, and it's done in a somewhat... unconventional fashion. Today we've got a problem where the generated JS needs to understand the types of each function exported or imported. This understanding is what enables it to generate the appropriate JS wrappers and such. We want to, however, be quite flexible and extensible in types that are supported across the boundary, which means that internally we rely on the trait system to resolve what's what. Communicating the type information historically was done by creating a four byte "descriptor" and using associated type projections to communicate that to the CLI tool. Unfortunately four bytes isn't a lot of space to cram information like arguments to a generic function, tuple types, etc. In general this just wasn't flexible enough and the way custom references were treated was also already a bit of a hack. This commit takes a radical step of creating a **descriptor function** for each function imported/exported. The really crazy part is that the `wasm-bindgen` CLI tool now embeds a wasm interpreter and executes these functions when the CLI tool is invoked. By allowing arbitrary functions to get executed it's now *much* easier to inform `wasm-bindgen` about complicated structures of types. Rest assured though that all these descriptor functions are automatically unexported and gc'd away, so this should not have any impact on binary sizes A new internal trait, `WasmDescribe`, is added to represent a description of all types, sort of like a serialization of the structure of a type that `wasm-bindgen` can understand. This works by calling a special exported function with a `u32` value a bunch of times. This means that when we run a descriptor we effectively get a `Vec<u32>` in the `wasm-bindgen` CLI tool. This list of integers can then be parsed into a rich `enum` for the JS generation to work with. This commit currently only retains feature parity with the previous implementation. I hope to soon solve issues like #123, #104, and #111 with this support.
2018-04-13 07:33:46 -07:00
}
}
fn shared(&self) -> shared::Export {
shared::Export {
class: self.class.map(|s| s.as_ref().to_string()),
method: self.method,
2018-04-14 16:41:41 +02:00
constructor: self.constructor.clone(),
Overhaul how type information gets to the CLI This commit is a complete overhaul of how the `#[wasm_bindgen]` macro communicates type information to the CLI tool, and it's done in a somewhat... unconventional fashion. Today we've got a problem where the generated JS needs to understand the types of each function exported or imported. This understanding is what enables it to generate the appropriate JS wrappers and such. We want to, however, be quite flexible and extensible in types that are supported across the boundary, which means that internally we rely on the trait system to resolve what's what. Communicating the type information historically was done by creating a four byte "descriptor" and using associated type projections to communicate that to the CLI tool. Unfortunately four bytes isn't a lot of space to cram information like arguments to a generic function, tuple types, etc. In general this just wasn't flexible enough and the way custom references were treated was also already a bit of a hack. This commit takes a radical step of creating a **descriptor function** for each function imported/exported. The really crazy part is that the `wasm-bindgen` CLI tool now embeds a wasm interpreter and executes these functions when the CLI tool is invoked. By allowing arbitrary functions to get executed it's now *much* easier to inform `wasm-bindgen` about complicated structures of types. Rest assured though that all these descriptor functions are automatically unexported and gc'd away, so this should not have any impact on binary sizes A new internal trait, `WasmDescribe`, is added to represent a description of all types, sort of like a serialization of the structure of a type that `wasm-bindgen` can understand. This works by calling a special exported function with a `u32` value a bunch of times. This means that when we run a descriptor we effectively get a `Vec<u32>` in the `wasm-bindgen` CLI tool. This list of integers can then be parsed into a rich `enum` for the JS generation to work with. This commit currently only retains feature parity with the previous implementation. I hope to soon solve issues like #123, #104, and #111 with this support.
2018-04-13 07:33:46 -07:00
function: self.function.shared(),
}
}
}
impl Enum {
fn shared(&self) -> shared::Enum {
shared::Enum {
name: self.name.as_ref().to_string(),
variants: self.variants.iter().map(|v| v.shared()).collect(),
}
}
}
impl Variant {
fn shared(&self) -> shared::EnumVariant {
shared::EnumVariant {
name: self.name.as_ref().to_string(),
value: self.value,
}
}
}
impl Import {
fn shared(&self) -> shared::Import {
shared::Import {
module: self.module.clone(),
js_namespace: self.js_namespace.map(|s| s.as_ref().to_string()),
kind: self.kind.shared(),
}
}
}
impl ImportKind {
fn shared(&self) -> shared::ImportKind {
match *self {
ImportKind::Function(ref f) => shared::ImportKind::Function(f.shared()),
ImportKind::Static(ref f) => shared::ImportKind::Static(f.shared()),
ImportKind::Type(ref f) => shared::ImportKind::Type(f.shared()),
}
2017-12-18 12:39:14 -08:00
}
2017-12-14 19:31:01 -08:00
}
2018-02-05 14:24:25 -08:00
impl ImportFunction {
pub fn infer_getter_property(&self) -> String {
2018-02-14 13:16:02 -08:00
self.function.name.as_ref().to_string()
}
pub fn infer_setter_property(&self) -> String {
2018-02-14 13:16:02 -08:00
let name = self.function.name.as_ref();
assert!(name.starts_with("set_"), "setters must start with `set_`");
name[4..].to_string()
}
Overhaul how type information gets to the CLI This commit is a complete overhaul of how the `#[wasm_bindgen]` macro communicates type information to the CLI tool, and it's done in a somewhat... unconventional fashion. Today we've got a problem where the generated JS needs to understand the types of each function exported or imported. This understanding is what enables it to generate the appropriate JS wrappers and such. We want to, however, be quite flexible and extensible in types that are supported across the boundary, which means that internally we rely on the trait system to resolve what's what. Communicating the type information historically was done by creating a four byte "descriptor" and using associated type projections to communicate that to the CLI tool. Unfortunately four bytes isn't a lot of space to cram information like arguments to a generic function, tuple types, etc. In general this just wasn't flexible enough and the way custom references were treated was also already a bit of a hack. This commit takes a radical step of creating a **descriptor function** for each function imported/exported. The really crazy part is that the `wasm-bindgen` CLI tool now embeds a wasm interpreter and executes these functions when the CLI tool is invoked. By allowing arbitrary functions to get executed it's now *much* easier to inform `wasm-bindgen` about complicated structures of types. Rest assured though that all these descriptor functions are automatically unexported and gc'd away, so this should not have any impact on binary sizes A new internal trait, `WasmDescribe`, is added to represent a description of all types, sort of like a serialization of the structure of a type that `wasm-bindgen` can understand. This works by calling a special exported function with a `u32` value a bunch of times. This means that when we run a descriptor we effectively get a `Vec<u32>` in the `wasm-bindgen` CLI tool. This list of integers can then be parsed into a rich `enum` for the JS generation to work with. This commit currently only retains feature parity with the previous implementation. I hope to soon solve issues like #123, #104, and #111 with this support.
2018-04-13 07:33:46 -07:00
fn shared(&self) -> shared::ImportFunction {
let mut method = false;
let mut js_new = false;
let mut class_name = None;
match self.kind {
ImportFunctionKind::Method { ref class, .. } => {
method = true;
class_name = Some(class);
}
ImportFunctionKind::JsConstructor { ref class, .. } => {
js_new = true;
class_name = Some(class);
}
ImportFunctionKind::Normal => {}
}
let mut getter = None;
let mut setter = None;
if let Some(s) = self.function.opts.getter() {
let s = s.map(|s| s.to_string());
getter = Some(s.unwrap_or_else(|| self.infer_getter_property()));
}
if let Some(s) = self.function.opts.setter() {
let s = s.map(|s| s.to_string());
setter = Some(s.unwrap_or_else(|| self.infer_setter_property()));
}
shared::ImportFunction {
shim: self.shim.as_ref().to_string(),
catch: self.function.opts.catch(),
method,
js_new,
structural: self.function.opts.structural(),
getter,
setter,
class: class_name.cloned(),
function: self.function.shared(),
}
}
}
impl ImportStatic {
fn shared(&self) -> shared::ImportStatic {
shared::ImportStatic {
name: self.js_name.as_ref().to_string(),
shim: self.shim.as_ref().to_string(),
}
}
}
impl ImportType {
fn shared(&self) -> shared::ImportType {
shared::ImportType { }
}
}
impl Struct {
fn from(s: syn::ItemStruct, _opts: BindgenAttrs) -> Struct {
let mut fields = Vec::new();
if let syn::Fields::Named(names) = s.fields {
for field in names.named.iter() {
match field.vis {
syn::Visibility::Public(..) => {}
_ => continue,
}
let name = match field.ident {
Some(n) => n,
None => continue,
};
let getter = shared::struct_field_get(s.ident.as_ref(), name.as_ref());
let setter = shared::struct_field_set(s.ident.as_ref(), name.as_ref());
fields.push(StructField {
name,
struct_name: s.ident,
ty: field.ty.clone(),
getter: getter.into(),
setter: setter.into(),
});
}
}
Struct {
name: s.ident,
fields,
}
}
fn shared(&self) -> shared::Struct {
shared::Struct {
name: self.name.as_ref().to_string(),
fields: self.fields.iter().map(|s| s.shared()).collect(),
}
}
}
impl StructField {
fn shared(&self) -> shared::StructField {
shared::StructField {
name: self.name.as_ref().to_string(),
}
2018-02-05 14:24:25 -08:00
}
}
#[derive(Default)]
pub struct BindgenAttrs {
attrs: Vec<BindgenAttr>,
}
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,
})
.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 js_namespace(&self) -> Option<syn::Ident> {
self.attrs
.iter()
.filter_map(|a| match *a {
BindgenAttr::JsNamespace(s) => Some(s),
_ => None,
})
.next()
}
2018-02-14 13:16:02 -08:00
pub fn getter(&self) -> Option<Option<syn::Ident>> {
self.attrs
.iter()
.filter_map(|a| match *a {
BindgenAttr::Getter(s) => Some(s),
_ => None,
2018-02-14 13:16:02 -08:00
})
.next()
2018-02-14 13:16:02 -08:00
}
pub fn setter(&self) -> Option<Option<syn::Ident>> {
self.attrs
.iter()
.filter_map(|a| match *a {
BindgenAttr::Setter(s) => Some(s),
_ => None,
2018-02-14 13:16:02 -08:00
})
.next()
2018-02-14 13:16:02 -08:00
}
pub fn structural(&self) -> bool {
self.attrs.iter().any(|a| match *a {
BindgenAttr::Structural => true,
_ => false,
})
}
pub fn js_name(&self) -> Option<syn::Ident> {
self.attrs
.iter()
.filter_map(|a| match *a {
BindgenAttr::JsName(s) => Some(s),
_ => None,
})
.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() } }
));
}
#[derive(PartialEq)]
enum BindgenAttr {
Catch,
Constructor,
Method,
JsNamespace(syn::Ident),
Module(String),
Getter(Option<syn::Ident>),
Setter(Option<syn::Ident>),
Structural,
JsName(syn::Ident),
}
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, "getter") >>
val: option!(do_parse!(
punct!(=) >>
s: syn!(syn::Ident) >>
(s)
)) >>
(val)
)=> { BindgenAttr::Getter }
2018-02-14 13:16:02 -08:00
|
do_parse!(
call!(term, "setter") >>
val: option!(do_parse!(
punct!(=) >>
s: syn!(syn::Ident) >>
(s)
)) >>
(val)
)=> { BindgenAttr::Setter }
2018-02-14 13:16:02 -08:00
|
call!(term, "structural") => { |_| BindgenAttr::Structural }
|
do_parse!(
call!(term, "js_namespace") >>
punct!(=) >>
ns: syn!(syn::Ident) >>
(ns)
)=> { BindgenAttr::JsNamespace }
|
do_parse!(
call!(term, "module") >>
punct!(=) >>
s: syn!(syn::LitStr) >>
(s.value())
)=> { BindgenAttr::Module }
|
do_parse!(
call!(term, "js_name") >>
punct!(=) >>
ns: syn!(syn::Ident) >>
(ns)
)=> { BindgenAttr::JsName }
));
}
fn extract_first_ty_param(ty: Option<&syn::Type>) -> Option<Option<syn::Type>> {
let t = match ty {
Some(t) => t,
None => return Some(None),
};
let path = match *t {
syn::Type::Path(syn::TypePath {
qself: None,
ref path,
}) => path,
_ => return None,
};
let seg = path.segments.last()?.into_value();
let generics = match seg.arguments {
syn::PathArguments::AngleBracketed(ref t) => t,
_ => return None,
};
let ty = match *generics.args.first()?.into_value() {
syn::GenericArgument::Type(ref t) => t,
_ => return None,
};
match *ty {
syn::Type::Tuple(ref t) if t.elems.len() == 0 => return Some(None),
_ => {}
}
Some(Some(ty.clone()))
}
fn term<'a>(cursor: syn::buffer::Cursor<'a>, name: &str) -> syn::synom::PResult<'a, ()> {
2018-04-03 07:07:14 -07:00
if let Some((term, next)) = cursor.term() {
if term.as_str() == name {
return Ok(((), next));
}
}
syn::parse_error()
}
fn assert_no_lifetimes(decl: &mut syn::FnDecl) {
struct Walk;
impl<'ast> syn::visit_mut::VisitMut for Walk {
fn visit_lifetime_mut(&mut self, _i: &mut syn::Lifetime) {
panic!("it is currently not sound to use lifetimes in function \
signatures");
}
}
syn::visit_mut::VisitMut::visit_fn_decl_mut(&mut Walk, decl);
}
fn replace_self(name: syn::Ident, item: &mut syn::ImplItem) {
struct Walk(syn::Ident);
impl syn::visit_mut::VisitMut for Walk {
fn visit_ident_mut(&mut self, i: &mut syn::Ident) {
if i.as_ref() == "Self" {
*i = self.0;
}
}
}
syn::visit_mut::VisitMut::visit_impl_item_mut(&mut Walk(name), item);
}