1
0
mirror of https://github.com/fluencelabs/wasm-bindgen synced 2025-03-25 22:41:04 +00:00

761 lines
23 KiB
Rust
Raw Normal View History

use literal::{self, Literal};
2018-01-08 10:42:01 -08:00
use proc_macro2::Span;
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,
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<Type>,
pub ret: Option<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,
}
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
}
2017-12-14 19:31:01 -08:00
pub enum Type {
ByRef(syn::Type),
ByMutRef(syn::Type),
ByValue(syn::Type),
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,
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);
}
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: 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.into_iter() {
self.push_impl_item(name, item);
2017-12-18 12:39:14 -08:00
}
}
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,
});
}
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 {
Type::ByRef(ref t) | Type::ByValue(ref t) => t,
Type::ByMutRef(_) => panic!("first method argument cannot be mutable ref"),
};
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(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");
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(),
})
}
pub fn literal(&self, dst: &mut Tokens) -> usize {
let mut tmp = Tokens::new();
let cnt = {
let mut a = literal::LiteralBuilder::new(&mut tmp);
Literal::literal(self, &mut a);
a.finish()
};
let cnt = cnt as u32;
(quote! {
(#cnt >> 0) as u8,
(#cnt >> 8) as u8,
(#cnt >> 16) as u8,
(#cnt >> 24) as u8
}).to_tokens(dst);
tmp.to_tokens(dst);
(cnt as usize) + 4
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,
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")
}
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| Type::from(&arg.ty))
.collect::<Vec<_>>();
let ret = match decl.output {
2017-12-14 19:31:01 -08:00
syn::ReturnType::Default => None,
2017-12-31 14:40:57 -08:00
syn::ReturnType::Type(_, ref t) => Some(Type::from(t)),
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
}
}
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)
}
2017-12-14 19:31:01 -08:00
impl Type {
pub fn from(ty: &syn::Type) -> Type {
Start removal of vector special-casing This commit starts wasm-bindgen down a path of removing the special casing it currently has around vectors, slices, and strings. This has long been a thorn in wasm-bindgen's side as it doesn't handle other kinds of vectors and otherwise is very inflexible with future additions. Additionally it leads to a lot of duplicated-ish code throughout various portions of codegen. The fundamental reason for this was that two arguments were required to be passed back to wasm, and I couldn't figure out a way to shove both those arguments into a function argument. The new strategy here is that there is one global stack well known to both JS and Rust which arguments *may* also be transferred between. By default all ABI arguments pass as literal function arguments, but if two or more arguments need to be passed then the extra ones are all passed through this global stack. The stack is effectively temporary scratch space when crossing the JS/Rust boundary (both ways). No long term storage is intended here. The `simple` test is passing as a result of this commit, using strings internally. The `Vector` type in the AST has been removed (yay!) and the bulk of the implementation of slices and vectors now resides in the `wasm-bindgen` crate itself, defining how to pass all these arguments around. The JS generator, however, still needs to know about all the sorts of vectors so it can generate appropriate code for JS. Future commits will continue cleanup and get the rest of the tests working.
2018-03-31 07:57:47 -07:00
if let syn::Type::Reference(ref r) = *ty {
return if r.mutability.is_some() {
Type::ByMutRef((*r.elem).clone())
} else {
Type::ByRef((*r.elem).clone())
2017-12-20 07:35:14 -08:00
}
2017-12-14 19:31:01 -08:00
}
Type::ByValue(ty.clone())
2017-12-14 19:31:01 -08:00
}
}
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
}
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())
2017-12-14 21:55:21 -08:00
}
None => shared::free_function_export_name(self.function.name.as_ref()),
};
2018-04-03 07:07:14 -07:00
syn::LitStr::new(&name, Span::call_site())
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()
}
}
impl Struct {
fn from(s: syn::ItemStruct, _opts: BindgenAttrs) -> Struct {
Struct { name: s.ident }
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() } }
));
}
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<&Type>) -> Option<Option<Type>> {
let ty = match ty {
Some(t) => t,
None => return Some(None),
};
let ty = match *ty {
Type::ByValue(ref t) => t,
_ => return None,
};
let path = match *ty {
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(Type::from(ty)))
}
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()
}