diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index d1d41903..87f5590e 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -1,8 +1,6 @@ -use proc_macro2::{Ident, Span, TokenStream, TokenTree}; -use quote::ToTokens; +use proc_macro2::{Ident, Span}; use shared; use syn; -use util; #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[derive(Default)] @@ -50,6 +48,8 @@ pub struct ImportFunction { pub function: Function, pub rust_name: Ident, pub js_ret: Option, + pub catch: bool, + pub structural: bool, pub kind: ImportFunctionKind, pub shim: Ident, } @@ -66,9 +66,21 @@ pub enum ImportFunctionKind { #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] pub enum MethodKind { - Normal, Constructor, - Static, + Operation(Operation), +} + +#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] +pub struct Operation { + pub is_static: bool, + pub kind: OperationKind, +} + +#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] +pub enum OperationKind { + Regular, + Setter(Option), + Getter(Option), } #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] @@ -92,7 +104,6 @@ pub struct Function { pub name: Ident, pub arguments: Vec, pub ret: Option, - pub opts: BindgenAttrs, pub rust_attrs: Vec, pub rust_vis: syn::Visibility, } @@ -106,9 +117,9 @@ pub struct Struct { #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] pub struct StructField { - pub opts: BindgenAttrs, pub name: Ident, pub struct_name: Ident, + pub readonly: bool, pub ty: syn::Type, pub getter: Ident, pub setter: Ident, @@ -151,349 +162,7 @@ pub struct TypeAlias { } impl Program { - pub fn push_item( - &mut self, - item: syn::Item, - opts: Option, - tokens: &mut TokenStream, - ) { - 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); - } - _ => {} - } - let comments = extract_doc_comments(&f.attrs); - f.to_tokens(tokens); - self.exports.push(Export { - class: None, - method_self: None, - constructor: None, - function: Function::from(f, opts), - comments, - }); - } - syn::Item::Struct(mut s) => { - let opts = opts.unwrap_or_else(|| BindgenAttrs::find(&mut s.attrs)); - self.structs.push(Struct::from(&mut s, opts)); - s.to_tokens(tokens); - } - 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); - } - 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) { - if item.defaultness.is_some() { - panic!("default impls are not supported"); - } - if item.unsafety.is_some() { - panic!("unsafe impls are not supported"); - } - 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"), - }, - _ => panic!("unsupported self type in impl"), - }; - for item in item.items.iter_mut() { - self.push_impl_item(&name, item); - } - } - - fn push_impl_item(&mut self, class: &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); - let comments = extract_doc_comments(&method.attrs); - let is_constructor = opts.constructor(); - let constructor = if is_constructor { - Some(method.sig.ident.to_string()) - } else { - None - }; - - let (function, method_self) = 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.clone()), - method_self, - constructor, - function, - comments, - }); - } - - pub fn push_enum(&mut self, item: syn::ItemEnum, _opts: BindgenAttrs) { - 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() > ::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.clone(), - value, - } - }) - .collect(); - let comments = extract_doc_comments(&item.attrs); - self.enums.push(Enum { - name: item.ident, - variants, - comments, - }); - } - - 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 version = item_opts - .version() - .or(opts.version()) - .map(|s| s.to_string()); - let js_namespace = item_opts.js_namespace().or(opts.js_namespace()).cloned(); - 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, - version, - 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).clone(); - let wasm = Function::from_decl(&js_name, f.decl, f.attrs, opts, f.vis, false).0; - let js_ret = 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... - extract_first_ty_param(wasm.ret.as_ref()) - .expect("can't `catch` without returning a Result") - } else { - wasm.ret.clone() - }; - - let kind = if wasm.opts.method() { - let class = wasm - .arguments - .get(0) - .expect("methods must have at least one argument"); - let class = match class.ty { - 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"); - let class_name = wasm - .opts - .js_class() - .map(Into::into) - .unwrap_or_else(|| class_name.to_string()); - - ImportFunctionKind::Method { - class: class_name, - ty: class.clone(), - kind: MethodKind::Normal, - } - } else if let Some(cls) = wasm.opts.static_method_of() { - let class = cls.to_string(); - let kind = MethodKind::Static; - let ty = util::ident_ty(cls.clone()); - ImportFunctionKind::Method { class, ty, kind } - } 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::Method { - class: class_name.to_string(), - ty: class.clone(), - kind: MethodKind::Constructor, - } - } else { - ImportFunctionKind::Normal - }; - - let shim = { - let ns = match kind { - ImportFunctionKind::Normal => "n", - ImportFunctionKind::Method { ref class, .. } => class, - }; - format!("__wbg_f_{}_{}_{}", js_name, f.ident, ns) - }; - ImportKind::Function(ImportFunction { - function: wasm, - kind, - js_ret, - rust_name: f.ident.clone(), - shim: Ident::new(&shim, Span::call_site()), - }) - } - - pub fn push_foreign_ty(&mut self, f: syn::ForeignItemType) -> ImportKind { - ImportKind::Type(ImportType { - vis: f.vis, - name: f.ident, - attrs: f.attrs, - }) - } - - 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.clone(), - js_name: js_name.clone(), - shim: Ident::new(&shim, Span::call_site()), - }) - } - - pub fn shared(&self) -> shared::Program { + pub(crate) 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(), @@ -506,88 +175,6 @@ impl Program { } impl Function { - pub fn from(input: syn::ItemFn, opts: BindgenAttrs) -> Function { - match input.vis { - syn::Visibility::Public(_) => {} - _ => panic!("can only bindgen public functions"), - } - if input.constness.is_some() { - panic!("can only bindgen non-const functions"); - } - if input.unsafety.is_some() { - panic!("can only bindgen safe functions"); - } - - Function::from_decl( - &input.ident, - input.decl, - input.attrs, - opts, - input.vis, - false, - ).0 - } - - pub fn from_decl( - name: &Ident, - mut decl: Box, - attrs: Vec, - opts: BindgenAttrs, - vis: syn::Visibility, - allow_self: bool, - ) -> (Function, Option) { - if decl.variadic.is_some() { - panic!("can't bindgen variadic functions") - } - if decl.generics.params.len() > 0 { - panic!("can't bindgen functions with lifetime or type parameters") - } - - assert_no_lifetimes(&mut decl); - - let syn::FnDecl { inputs, output, .. } = { *decl }; - - let mut method_self = None; - let arguments = inputs - .into_iter() - .filter_map(|arg| match arg { - syn::FnArg::Captured(c) => Some(c), - syn::FnArg::SelfValue(_) => { - assert!(method_self.is_none()); - method_self = Some(MethodSelf::ByValue); - None - } - syn::FnArg::SelfRef(ref a) if allow_self => { - assert!(method_self.is_none()); - if a.mutability.is_some() { - method_self = Some(MethodSelf::RefMutable); - } else { - method_self = Some(MethodSelf::RefShared); - } - None - } - _ => panic!("arguments cannot be `self` or ignored"), - }) - .collect::>(); - - let ret = match output { - syn::ReturnType::Default => None, - syn::ReturnType::Type(_, ty) => Some(*ty), - }; - - ( - Function { - name: name.clone(), - arguments, - ret, - opts, - rust_vis: vis, - rust_attrs: attrs, - }, - method_self, - ) - } - fn shared(&self) -> shared::Function { shared::Function { name: self.name.to_string(), @@ -595,22 +182,8 @@ impl Function { } } -pub fn extract_path_ident(path: &syn::Path) -> Option { - if path.leading_colon.is_some() { - return None; - } - if path.segments.len() != 1 { - return None; - } - match path.segments.first().unwrap().value().arguments { - syn::PathArguments::None => {} - _ => return None, - } - path.segments.first().map(|v| v.value().ident.clone()) -} - impl Export { - pub fn rust_symbol(&self) -> Ident { + pub(crate) fn rust_symbol(&self) -> Ident { let mut generated_name = format!("__wasm_bindgen_generated"); if let Some(class) = &self.class { generated_name.push_str("_"); @@ -621,7 +194,7 @@ impl Export { Ident::new(&generated_name, Span::call_site()) } - pub fn export_name(&self) -> String { + pub(crate) fn export_name(&self) -> String { let fn_name = self.function.name.to_string(); match &self.class { Some(class) => shared::struct_function_export_name(&class.to_string(), &fn_name), @@ -719,56 +292,58 @@ impl ImportKind { } impl ImportFunction { - pub fn infer_getter_property(&self) -> String { + fn infer_getter_property(&self) -> String { self.function.name.to_string() } - pub fn infer_setter_property(&self) -> String { + fn infer_setter_property(&self) -> String { let name = self.function.name.to_string(); assert!(name.starts_with("set_"), "setters must start with `set_`"); name[4..].to_string() } fn shared(&self) -> shared::ImportFunction { - 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())); - } - - let mut method = None; - match self.kind { + let method = match self.kind { ImportFunctionKind::Method { ref class, ref kind, .. } => { let kind = match kind { - MethodKind::Normal => shared::MethodKind::Normal, MethodKind::Constructor => shared::MethodKind::Constructor, - MethodKind::Static => shared::MethodKind::Static, + MethodKind::Operation(Operation { is_static, kind }) => { + let is_static = *is_static; + let kind = match kind { + OperationKind::Regular => shared::OperationKind::Regular, + OperationKind::Getter(g) => { + let g = g.as_ref().map(|g| g.to_string()); + shared::OperationKind::Getter( + g.unwrap_or_else(|| self.infer_getter_property()), + ) + } + OperationKind::Setter(s) => { + let s = s.as_ref().map(|s| s.to_string()); + shared::OperationKind::Setter( + s.unwrap_or_else(|| self.infer_setter_property()), + ) + } + }; + shared::MethodKind::Operation(shared::Operation { is_static, kind }) + } }; - method = Some(shared::MethodData { + Some(shared::MethodData { class: class.clone(), kind, - getter, - setter, - }); + }) } - ImportFunctionKind::Normal => {} - } + ImportFunctionKind::Normal => None, + }; shared::ImportFunction { shim: self.shim.to_string(), - catch: self.function.opts.catch(), + catch: self.catch, method, - structural: self.function.opts.structural(), + structural: self.structural, function: self.function.shared(), } } @@ -790,49 +365,6 @@ impl ImportType { } impl Struct { - fn from(s: &mut syn::ItemStruct, _opts: BindgenAttrs) -> Struct { - if s.generics.params.len() > 0 { - panic!( - "structs with #[wasm_bindgen] cannot have lifetime or \ - type parameters currently" - ); - } - let mut fields = Vec::new(); - if let syn::Fields::Named(names) = &mut s.fields { - for field in names.named.iter_mut() { - match field.vis { - syn::Visibility::Public(..) => {} - _ => continue, - } - let name = match &field.ident { - Some(n) => n, - None => continue, - }; - let ident = s.ident.to_string(); - let name_str = name.to_string(); - let getter = shared::struct_field_get(&ident, &name_str); - let setter = shared::struct_field_set(&ident, &name_str); - let opts = BindgenAttrs::find(&mut field.attrs); - let comments = extract_doc_comments(&field.attrs); - fields.push(StructField { - opts, - name: name.clone(), - struct_name: s.ident.clone(), - ty: field.ty.clone(), - getter: Ident::new(&getter, Span::call_site()), - setter: Ident::new(&setter, Span::call_site()), - comments, - }); - } - } - let comments: Vec = extract_doc_comments(&s.attrs); - Struct { - name: s.ident.clone(), - fields, - comments, - } - } - fn shared(&self) -> shared::Struct { shared::Struct { name: self.name.to_string(), @@ -846,361 +378,8 @@ impl StructField { fn shared(&self) -> shared::StructField { shared::StructField { name: self.name.to_string(), - readonly: self.opts.readonly(), + readonly: self.readonly, comments: self.comments.clone(), } } } - -#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] -#[derive(Default)] -pub struct BindgenAttrs { - pub attrs: Vec, -} - -impl BindgenAttrs { - pub fn find(attrs: &mut Vec) -> 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(), - }; - let mut tts = attrs.remove(pos).tts.into_iter(); - let tt = match tts.next() { - Some(TokenTree::Group(d)) => d.stream(), - Some(_) => panic!("malformed #[wasm_bindgen] attribute"), - None => return BindgenAttrs::default(), - }; - if tts.next().is_some() { - panic!("malformed #[wasm_bindgen] attribute"); - } - syn::parse(tt.into()).expect("malformed #[wasm_bindgen] attribute") - } - - fn module(&self) -> Option<&str> { - self.attrs - .iter() - .filter_map(|a| match a { - BindgenAttr::Module(s) => Some(&s[..]), - _ => None, - }) - .next() - } - - fn version(&self) -> Option<&str> { - self.attrs - .iter() - .filter_map(|a| match a { - BindgenAttr::Version(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 static_method_of(&self) -> Option<&Ident> { - self.attrs - .iter() - .filter_map(|a| match a { - BindgenAttr::StaticMethodOf(c) => Some(c), - _ => None, - }) - .next() - } - - fn method(&self) -> bool { - self.attrs.iter().any(|a| match a { - BindgenAttr::Method => true, - _ => false, - }) - } - - fn js_namespace(&self) -> Option<&Ident> { - self.attrs - .iter() - .filter_map(|a| match a { - BindgenAttr::JsNamespace(s) => Some(s), - _ => None, - }) - .next() - } - - pub fn getter(&self) -> Option> { - self.attrs - .iter() - .filter_map(|a| match a { - BindgenAttr::Getter(s) => Some(s.as_ref()), - _ => None, - }) - .next() - } - - pub fn setter(&self) -> Option> { - self.attrs - .iter() - .filter_map(|a| match a { - BindgenAttr::Setter(s) => Some(s.as_ref()), - _ => None, - }) - .next() - } - - pub fn structural(&self) -> bool { - self.attrs.iter().any(|a| match *a { - BindgenAttr::Structural => true, - _ => false, - }) - } - - pub fn readonly(&self) -> bool { - self.attrs.iter().any(|a| match *a { - BindgenAttr::Readonly => true, - _ => false, - }) - } - - pub fn js_name(&self) -> Option<&Ident> { - self.attrs - .iter() - .filter_map(|a| match a { - BindgenAttr::JsName(s) => Some(s), - _ => None, - }) - .next() - } - - fn js_class(&self) -> Option<&str> { - self.attrs - .iter() - .filter_map(|a| match a { - BindgenAttr::JsClass(s) => Some(&s[..]), - _ => None, - }) - .next() - } -} - -impl syn::synom::Synom for BindgenAttrs { - named!(parse -> Self, alt!( - do_parse!( - opts: call!( - syn::punctuated::Punctuated::<_, syn::token::Comma>::parse_terminated - ) >> - (BindgenAttrs { - attrs: opts.into_iter().collect(), - }) - ) => { |s| s } - | - epsilon!() => { |_| BindgenAttrs { attrs: Vec::new() } } - )); -} - -#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] -pub enum BindgenAttr { - Catch, - Constructor, - Method, - StaticMethodOf(Ident), - JsNamespace(Ident), - Module(String), - Version(String), - Getter(Option), - Setter(Option), - Structural, - Readonly, - JsName(Ident), - JsClass(String), -} - -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, "static_method_of") >> - punct!(=) >> - cls: call!(term2ident) >> - (cls) - )=> { BindgenAttr::StaticMethodOf } - | - do_parse!( - call!(term, "getter") >> - val: option!(do_parse!( - punct!(=) >> - s: call!(term2ident) >> - (s) - )) >> - (val) - )=> { BindgenAttr::Getter } - | - do_parse!( - call!(term, "setter") >> - val: option!(do_parse!( - punct!(=) >> - s: call!(term2ident) >> - (s) - )) >> - (val) - )=> { BindgenAttr::Setter } - | - call!(term, "structural") => { |_| BindgenAttr::Structural } - | - call!(term, "readonly") => { |_| BindgenAttr::Readonly } - | - do_parse!( - call!(term, "js_namespace") >> - punct!(=) >> - ns: call!(term2ident) >> - (ns) - )=> { BindgenAttr::JsNamespace } - | - do_parse!( - call!(term, "module") >> - punct!(=) >> - s: syn!(syn::LitStr) >> - (s.value()) - )=> { BindgenAttr::Module } - | - do_parse!( - call!(term, "version") >> - punct!(=) >> - s: syn!(syn::LitStr) >> - (s.value()) - )=> { BindgenAttr::Version } - | - do_parse!( - call!(term, "js_name") >> - punct!(=) >> - ns: call!(term2ident) >> - (ns) - )=> { BindgenAttr::JsName } - | - do_parse!( - call!(term, "js_class") >> - punct!(=) >> - s: syn!(syn::LitStr) >> - (s.value()) - )=> { BindgenAttr::JsClass } - )); -} - -fn extract_first_ty_param(ty: Option<&syn::Type>) -> Option> { - 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, ()> { - if let Some((ident, next)) = cursor.ident() { - if ident == name { - return Ok(((), next)); - } - } - syn::parse_error() -} - -fn term2ident<'a>(cursor: syn::buffer::Cursor<'a>) -> syn::synom::PResult<'a, Ident> { - match cursor.ident() { - Some(pair) => Ok(pair), - None => 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: &Ident, item: &mut syn::ImplItem) { - struct Walk<'a>(&'a Ident); - - impl<'a> syn::visit_mut::VisitMut for Walk<'a> { - fn visit_ident_mut(&mut self, i: &mut Ident) { - if i == "Self" { - *i = self.0.clone(); - } - } - } - - syn::visit_mut::VisitMut::visit_impl_item_mut(&mut Walk(name), item); -} - -/// Extract the documentation comments from a Vec of attributes -fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec { - attrs - .iter() - .filter_map(|a| { - // if the path segments include an ident of "doc" we know this - // this is a doc comment - if a.path.segments.iter().any(|s| s.ident.to_string() == "doc") { - Some( - // We want to filter out any Puncts so just grab the Literals - a.tts.clone().into_iter().filter_map(|t| match t { - TokenTree::Literal(lit) => { - // this will always return the quoted string, we deal with - // that in the cli when we read in the comments - Some(lit.to_string()) - }, - _ => None, - }) - ) - } else { - None - } - }) - //Fold up the [[String]] iter we created into Vec - .fold(vec![], |mut acc, a| {acc.extend(a); acc}) -} diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 984bc4d5..0966f0c4 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -273,7 +273,7 @@ impl ToTokens for ast::StructField { } }).to_tokens(tokens); - if self.opts.readonly() { + if self.readonly { return; } @@ -356,12 +356,10 @@ impl ToTokens for ast::Export { }); quote! { me.#name } } - None => { - match &self.class { - Some(class) => quote! { #class::#name }, - None => quote! { #name } - } - } + None => match &self.class { + Some(class) => quote! { #class::#name }, + None => quote! { #name }, + }, }; for (i, syn::ArgCaptured { ty, .. }) in self.function.arguments.iter().enumerate() { @@ -591,7 +589,10 @@ impl ToTokens for ast::ImportFunction { ast::ImportFunctionKind::Method { ref ty, ref kind, .. } => { - if let ast::MethodKind::Normal = kind { + if let ast::MethodKind::Operation(ast::Operation { + is_static: false, .. + }) = kind + { is_method = true; } class_ty = Some(ty); @@ -618,9 +619,7 @@ impl ToTokens for ast::ImportFunction { subpat: None, .. }) => ident.clone(), - syn::Pat::Wild(_) => { - syn::Ident::new(&format!("__genarg_{}", i), Span::call_site()) - } + syn::Pat::Wild(_) => syn::Ident::new(&format!("__genarg_{}", i), Span::call_site()), _ => panic!("unsupported pattern in foreign function"), }; @@ -664,7 +663,7 @@ impl ToTokens for ast::ImportFunction { } let mut exceptional_ret = quote!(); - let exn_data = if self.function.opts.catch() { + let exn_data = if self.catch { let exn_data = Ident::new("exn_data", Span::call_site()); let exn_data_ptr = Ident::new("exn_data_ptr", Span::call_site()); abi_argument_names.push(exn_data_ptr.clone()); diff --git a/crates/backend/src/lib.rs b/crates/backend/src/lib.rs index 60a513da..40181999 100755 --- a/crates/backend/src/lib.rs +++ b/crates/backend/src/lib.rs @@ -4,9 +4,8 @@ extern crate proc_macro2; #[macro_use] extern crate quote; -#[macro_use] -extern crate syn; extern crate serde_json; +extern crate syn; extern crate wasm_bindgen_shared as shared; diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index e1e9d8a3..921c3d25 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -978,7 +978,8 @@ impl<'a> Context<'a> { return; } if self.config.nodejs_experimental_modules { - self.imports.push_str("import { TextEncoder } from 'util';\n"); + self.imports + .push_str("import { TextEncoder } from 'util';\n"); } else if self.config.nodejs { self.global( " @@ -1006,7 +1007,8 @@ impl<'a> Context<'a> { return; } if self.config.nodejs_experimental_modules { - self.imports.push_str("import { TextDecoder } from 'util';\n"); + self.imports + .push_str("import { TextDecoder } from 'util';\n"); } else if self.config.nodejs { self.global( " @@ -1788,7 +1790,7 @@ impl<'a, 'b> SubContext<'a, 'b> { import: &shared::ImportFunction, ) -> Result<(), Error> { if !self.cx.wasm_import_needed(&import.shim) { - return Ok(()) + return Ok(()); } let descriptor = match self.cx.describe(&import.shim) { @@ -1797,101 +1799,85 @@ impl<'a, 'b> SubContext<'a, 'b> { }; let target = match &import.method { - Some(shared::MethodData { - class, - kind, - getter, - setter, - }) => { + Some(shared::MethodData { class, kind }) => { let class = self.import_name(info, class)?; - if let shared::MethodKind::Constructor = kind { - format!("new {}", class) - } else { - let is_static = if let shared::MethodKind::Static = kind { - true - } else { - false - }; + match kind { + shared::MethodKind::Constructor => format!("new {}", class), + shared::MethodKind::Operation(shared::Operation { is_static, kind }) => { + let target = if import.structural { + let location = if *is_static { &class } else { "this" }; - let target = if let Some(g) = getter { - if import.structural { - format!( - "function() {{ - return {}.{}; - }}", - if is_static { &class } else { "this" }, - g - ) - } else { - self.cx.expose_get_inherited_descriptor(); - format!( - "GetOwnOrInheritedPropertyDescriptor\ - ({}{}, '{}').get", - class, - if is_static { "" } else { ".prototype" }, - g, - ) - } - } else if let Some(s) = setter { - if import.structural { - format!( - "function(y) {{ - {}.{} = y; - }}", - if is_static { &class } else { "this" }, - s - ) - } else { - self.cx.expose_get_inherited_descriptor(); - format!( - "GetOwnOrInheritedPropertyDescriptor\ - ({}{}, '{}').set", - class, - if is_static { "" } else { ".prototype" }, - s, - ) - } - } else { - if import.structural { - let nargs = descriptor.unwrap_function().arguments.len(); - let mut s = format!("function("); - for i in 0..nargs - 1 { - if i > 0 { - drop(write!(s, ", ")); + match kind { + shared::OperationKind::Getter(g) => format!( + "function() {{ + return {}.{}; + }}", + location, g + ), + shared::OperationKind::Setter(s) => format!( + "function(y) {{ + {}.{} = y; + }}", + location, s + ), + shared::OperationKind::Regular => { + let nargs = descriptor.unwrap_function().arguments.len(); + let mut s = format!("function("); + for i in 0..nargs - 1 { + if i > 0 { + drop(write!(s, ", ")); + } + drop(write!(s, "x{}", i)); + } + s.push_str(") { \nreturn this."); + s.push_str(&import.function.name); + s.push_str("("); + for i in 0..nargs - 1 { + if i > 0 { + drop(write!(s, ", ")); + } + drop(write!(s, "x{}", i)); + } + s.push_str(");\n}"); + s } - drop(write!(s, "x{}", i)); } - s.push_str(") { \nreturn this."); - s.push_str(&import.function.name); - s.push_str("("); - for i in 0..nargs - 1 { - if i > 0 { - drop(write!(s, ", ")); - } - drop(write!(s, "x{}", i)); - } - s.push_str(");\n}"); - s } else { - format!( - "{}{}.{}", - class, - if is_static { "" } else { ".prototype" }, - import.function.name - ) - } - }; - self.cx.global(&format!( - " - const {}_target = {}; - ", - import.shim, target - )); - format!( - "{}_target{}", - import.shim, - if is_static { "" } else { ".call" } - ) + let location = if *is_static { "" } else { ".prototype" }; + + match kind { + shared::OperationKind::Getter(g) => { + self.cx.expose_get_inherited_descriptor(); + format!( + "GetOwnOrInheritedPropertyDescriptor({}{}, '{}').get", + class, location, g, + ) + } + shared::OperationKind::Setter(s) => { + self.cx.expose_get_inherited_descriptor(); + format!( + "GetOwnOrInheritedPropertyDescriptor({}{}, '{}').set", + class, location, s, + ) + } + shared::OperationKind::Regular => { + format!("{}{}.{}", class, location, import.function.name) + } + } + }; + + self.cx.global(&format!( + " + const {}_target = {}; + ", + import.shim, target + )); + format!( + "{}_target{}", + import.shim, + if *is_static { "" } else { ".call" } + ) + } } } None => { diff --git a/crates/macro/Cargo.toml b/crates/macro/Cargo.toml index fc0ec0b3..ec25b831 100644 --- a/crates/macro/Cargo.toml +++ b/crates/macro/Cargo.toml @@ -22,3 +22,4 @@ syn = { version = '0.14', features = ['full'] } quote = '0.6' proc-macro2 = "0.4.4" wasm-bindgen-backend = { path = "../backend", version = "=0.2.11" } +wasm-bindgen-shared = { path = "../shared", version = "=0.2.11" } diff --git a/crates/macro/src/lib.rs b/crates/macro/src/lib.rs index c53c8c45..c4f88ad8 100755 --- a/crates/macro/src/lib.rs +++ b/crates/macro/src/lib.rs @@ -3,21 +3,27 @@ extern crate proc_macro; extern crate proc_macro2; extern crate quote; +extern crate wasm_bindgen_shared as shared; +#[macro_use] extern crate syn; extern crate wasm_bindgen_backend as backend; use proc_macro::TokenStream; use quote::ToTokens; +mod parser; + +use parser::MacroParse; + #[proc_macro_attribute] pub fn wasm_bindgen(attr: TokenStream, input: TokenStream) -> TokenStream { let item = syn::parse::(input.clone()).expect("expected a valid Rust item"); - let opts = syn::parse::(attr) - .expect("invalid arguments to #[wasm_bindgen]"); + let opts = + syn::parse::(attr).expect("invalid arguments to #[wasm_bindgen]"); let mut ret = proc_macro2::TokenStream::new(); let mut program = backend::ast::Program::default(); - program.push_item(item, Some(opts), &mut ret); + item.macro_parse(&mut program, (Some(opts), &mut ret)); program.to_tokens(&mut ret); if cfg!(feature = "xxx_debug_only_print_generated_code") { diff --git a/crates/macro/src/parser.rs b/crates/macro/src/parser.rs new file mode 100644 index 00000000..93d97f72 --- /dev/null +++ b/crates/macro/src/parser.rs @@ -0,0 +1,879 @@ +use backend::{ast, util::ident_ty}; +use proc_macro2::{Ident, Span, TokenStream, TokenTree}; +use quote::ToTokens; +use shared; +use syn; + +#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] +#[derive(Default)] +pub struct BindgenAttrs { + pub attrs: Vec, +} + +impl BindgenAttrs { + fn find(attrs: &mut Vec) -> 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(), + }; + let mut tts = attrs.remove(pos).tts.into_iter(); + let tt = match tts.next() { + Some(TokenTree::Group(d)) => d.stream(), + Some(_) => panic!("malformed #[wasm_bindgen] attribute"), + None => return BindgenAttrs::default(), + }; + if tts.next().is_some() { + panic!("malformed #[wasm_bindgen] attribute"); + } + syn::parse(tt.into()).expect("malformed #[wasm_bindgen] attribute") + } + + fn module(&self) -> Option<&str> { + self.attrs + .iter() + .filter_map(|a| match a { + BindgenAttr::Module(s) => Some(&s[..]), + _ => None, + }) + .next() + } + + fn version(&self) -> Option<&str> { + self.attrs + .iter() + .filter_map(|a| match a { + BindgenAttr::Version(s) => Some(&s[..]), + _ => None, + }) + .next() + } + + 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 static_method_of(&self) -> Option<&Ident> { + self.attrs + .iter() + .filter_map(|a| match a { + BindgenAttr::StaticMethodOf(c) => Some(c), + _ => None, + }) + .next() + } + + fn method(&self) -> bool { + self.attrs.iter().any(|a| match a { + BindgenAttr::Method => true, + _ => false, + }) + } + + fn js_namespace(&self) -> Option<&Ident> { + self.attrs + .iter() + .filter_map(|a| match a { + BindgenAttr::JsNamespace(s) => Some(s), + _ => None, + }) + .next() + } + + fn getter(&self) -> Option> { + self.attrs + .iter() + .filter_map(|a| match a { + BindgenAttr::Getter(g) => Some(g.clone()), + _ => None, + }) + .next() + } + + fn setter(&self) -> Option> { + self.attrs + .iter() + .filter_map(|a| match a { + BindgenAttr::Setter(s) => Some(s.clone()), + _ => None, + }) + .next() + } + + fn structural(&self) -> bool { + self.attrs.iter().any(|a| match *a { + BindgenAttr::Structural => true, + _ => false, + }) + } + + fn readonly(&self) -> bool { + self.attrs.iter().any(|a| match *a { + BindgenAttr::Readonly => true, + _ => false, + }) + } + + fn js_name(&self) -> Option<&Ident> { + self.attrs + .iter() + .filter_map(|a| match a { + BindgenAttr::JsName(s) => Some(s), + _ => None, + }) + .next() + } + + fn js_class(&self) -> Option<&str> { + self.attrs + .iter() + .filter_map(|a| match a { + BindgenAttr::JsClass(s) => Some(&s[..]), + _ => None, + }) + .next() + } +} + +impl syn::synom::Synom for BindgenAttrs { + named!(parse -> Self, alt!( + do_parse!( + opts: call!( + syn::punctuated::Punctuated::<_, syn::token::Comma>::parse_terminated + ) >> + (BindgenAttrs { + attrs: opts.into_iter().collect(), + }) + ) => { |s| s } + | + epsilon!() => { |_| BindgenAttrs { attrs: Vec::new() } } + )); +} + +#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] +pub enum BindgenAttr { + Catch, + Constructor, + Method, + StaticMethodOf(Ident), + JsNamespace(Ident), + Module(String), + Version(String), + Getter(Option), + Setter(Option), + Structural, + Readonly, + JsName(Ident), + JsClass(String), +} + +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, "static_method_of") >> + punct!(=) >> + cls: call!(term2ident) >> + (cls) + )=> { BindgenAttr::StaticMethodOf } + | + do_parse!( + call!(term, "getter") >> + val: option!(do_parse!( + punct!(=) >> + s: call!(term2ident) >> + (s) + )) >> + (val) + )=> { BindgenAttr::Getter } + | + do_parse!( + call!(term, "setter") >> + val: option!(do_parse!( + punct!(=) >> + s: call!(term2ident) >> + (s) + )) >> + (val) + )=> { BindgenAttr::Setter } + | + call!(term, "structural") => { |_| BindgenAttr::Structural } + | + call!(term, "readonly") => { |_| BindgenAttr::Readonly } + | + do_parse!( + call!(term, "js_namespace") >> + punct!(=) >> + ns: call!(term2ident) >> + (ns) + )=> { BindgenAttr::JsNamespace } + | + do_parse!( + call!(term, "module") >> + punct!(=) >> + s: syn!(syn::LitStr) >> + (s.value()) + )=> { BindgenAttr::Module } + | + do_parse!( + call!(term, "version") >> + punct!(=) >> + s: syn!(syn::LitStr) >> + (s.value()) + )=> { BindgenAttr::Version } + | + do_parse!( + call!(term, "js_name") >> + punct!(=) >> + ns: call!(term2ident) >> + (ns) + )=> { BindgenAttr::JsName } + | + do_parse!( + call!(term, "js_class") >> + punct!(=) >> + s: syn!(syn::LitStr) >> + (s.value()) + )=> { BindgenAttr::JsClass } + )); +} + +fn term<'a>(cursor: syn::buffer::Cursor<'a>, name: &str) -> syn::synom::PResult<'a, ()> { + if let Some((ident, next)) = cursor.ident() { + if ident == name { + return Ok(((), next)); + } + } + syn::parse_error() +} + +fn term2ident<'a>(cursor: syn::buffer::Cursor<'a>) -> syn::synom::PResult<'a, Ident> { + match cursor.ident() { + Some(pair) => Ok(pair), + None => syn::parse_error(), + } +} + +trait ConvertToAst { + type Target; + fn convert(self, context: Ctx) -> Self::Target; +} + +impl<'a> ConvertToAst<()> for &'a mut syn::ItemStruct { + type Target = ast::Struct; + + fn convert(self, (): ()) -> Self::Target { + if self.generics.params.len() > 0 { + panic!( + "structs with #[wasm_bindgen] cannot have lifetime or \ + type parameters currently" + ); + } + let mut fields = Vec::new(); + if let syn::Fields::Named(names) = &mut self.fields { + for field in names.named.iter_mut() { + match field.vis { + syn::Visibility::Public(..) => {} + _ => continue, + } + let name = match &field.ident { + Some(n) => n, + None => continue, + }; + let ident = self.ident.to_string(); + let name_str = name.to_string(); + let getter = shared::struct_field_get(&ident, &name_str); + let setter = shared::struct_field_set(&ident, &name_str); + let opts = BindgenAttrs::find(&mut field.attrs); + let comments = extract_doc_comments(&field.attrs); + fields.push(ast::StructField { + name: name.clone(), + struct_name: self.ident.clone(), + readonly: opts.readonly(), + ty: field.ty.clone(), + getter: Ident::new(&getter, Span::call_site()), + setter: Ident::new(&setter, Span::call_site()), + comments, + }); + } + } + let comments: Vec = extract_doc_comments(&self.attrs); + ast::Struct { + name: self.ident.clone(), + fields, + comments, + } + } +} + +impl ConvertToAst for syn::ForeignItemFn { + type Target = ast::ImportKind; + + fn convert(self, opts: BindgenAttrs) -> Self::Target { + let js_name = opts.js_name().unwrap_or(&self.ident).clone(); + let wasm = function_from_decl(&js_name, self.decl, self.attrs, self.vis, false).0; + let catch = opts.catch(); + let js_ret = if 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... + extract_first_ty_param(wasm.ret.as_ref()) + .expect("can't `catch` without returning a Result") + } else { + wasm.ret.clone() + }; + + let mut operation_kind = ast::OperationKind::Regular; + if let Some(g) = opts.getter() { + operation_kind = ast::OperationKind::Getter(g); + } + if let Some(s) = opts.setter() { + operation_kind = ast::OperationKind::Setter(s); + } + + let kind = if opts.method() { + let class = wasm + .arguments + .get(0) + .expect("methods must have at least one argument"); + let class = match class.ty { + 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"); + let class_name = opts + .js_class() + .map(Into::into) + .unwrap_or_else(|| class_name.to_string()); + + let kind = ast::MethodKind::Operation(ast::Operation { + is_static: false, + kind: operation_kind, + }); + + ast::ImportFunctionKind::Method { + class: class_name, + ty: class.clone(), + kind, + } + } else if let Some(cls) = opts.static_method_of() { + let class = cls.to_string(); + let ty = ident_ty(cls.clone()); + + let kind = ast::MethodKind::Operation(ast::Operation { + is_static: true, + kind: operation_kind, + }); + + ast::ImportFunctionKind::Method { class, ty, kind } + } else if 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"); + + ast::ImportFunctionKind::Method { + class: class_name.to_string(), + ty: class.clone(), + kind: ast::MethodKind::Constructor, + } + } else { + ast::ImportFunctionKind::Normal + }; + + let shim = { + let ns = match kind { + ast::ImportFunctionKind::Normal => "n", + ast::ImportFunctionKind::Method { ref class, .. } => class, + }; + format!("__wbg_f_{}_{}_{}", js_name, self.ident, ns) + }; + ast::ImportKind::Function(ast::ImportFunction { + function: wasm, + kind, + js_ret, + catch, + structural: opts.structural(), + rust_name: self.ident.clone(), + shim: Ident::new(&shim, Span::call_site()), + }) + } +} + +impl ConvertToAst<()> for syn::ForeignItemType { + type Target = ast::ImportKind; + + fn convert(self, (): ()) -> Self::Target { + ast::ImportKind::Type(ast::ImportType { + vis: self.vis, + name: self.ident, + attrs: self.attrs, + }) + } +} + +impl ConvertToAst for syn::ForeignItemStatic { + type Target = ast::ImportKind; + + fn convert(self, opts: BindgenAttrs) -> Self::Target { + if self.mutability.is_some() { + panic!("cannot import mutable globals yet") + } + let js_name = opts.js_name().unwrap_or(&self.ident); + let shim = format!("__wbg_static_accessor_{}_{}", js_name, self.ident); + ast::ImportKind::Static(ast::ImportStatic { + ty: *self.ty, + vis: self.vis, + rust_name: self.ident.clone(), + js_name: js_name.clone(), + shim: Ident::new(&shim, Span::call_site()), + }) + } +} + +impl ConvertToAst<()> for syn::ItemFn { + type Target = ast::Function; + + fn convert(self, (): ()) -> Self::Target { + match self.vis { + syn::Visibility::Public(_) => {} + _ => panic!("can only bindgen public functions"), + } + if self.constness.is_some() { + panic!("can only bindgen non-const functions"); + } + if self.unsafety.is_some() { + panic!("can only bindgen safe functions"); + } + + function_from_decl(&self.ident, self.decl, self.attrs, self.vis, false).0 + } +} + +fn function_from_decl( + name: &Ident, + mut decl: Box, + attrs: Vec, + vis: syn::Visibility, + allow_self: bool, +) -> (ast::Function, Option) { + if decl.variadic.is_some() { + panic!("can't bindgen variadic functions") + } + if decl.generics.params.len() > 0 { + panic!("can't bindgen functions with lifetime or type parameters") + } + + assert_no_lifetimes(&mut decl); + + let syn::FnDecl { inputs, output, .. } = { *decl }; + + let mut method_self = None; + let arguments = inputs + .into_iter() + .filter_map(|arg| match arg { + syn::FnArg::Captured(c) => Some(c), + syn::FnArg::SelfValue(_) => { + assert!(method_self.is_none()); + method_self = Some(ast::MethodSelf::ByValue); + None + } + syn::FnArg::SelfRef(ref a) if allow_self => { + assert!(method_self.is_none()); + if a.mutability.is_some() { + method_self = Some(ast::MethodSelf::RefMutable); + } else { + method_self = Some(ast::MethodSelf::RefShared); + } + None + } + _ => panic!("arguments cannot be `self` or ignored"), + }) + .collect::>(); + + let ret = match output { + syn::ReturnType::Default => None, + syn::ReturnType::Type(_, ty) => Some(*ty), + }; + + ( + ast::Function { + name: name.clone(), + arguments, + ret, + rust_vis: vis, + rust_attrs: attrs, + }, + method_self, + ) +} + +pub(crate) trait MacroParse { + fn macro_parse(self, program: &mut ast::Program, context: Ctx); +} + +impl<'a> MacroParse<(Option, &'a mut TokenStream)> for syn::Item { + fn macro_parse( + self, + program: &mut ast::Program, + (opts, tokens): (Option, &'a mut TokenStream), + ) { + match self { + syn::Item::Fn(mut f) => { + 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); + } + _ => {} + } + let comments = extract_doc_comments(&f.attrs); + f.to_tokens(tokens); + program.exports.push(ast::Export { + class: None, + method_self: None, + constructor: None, + function: f.convert(()), + comments, + }); + } + syn::Item::Struct(mut s) => { + program.structs.push((&mut s).convert(())); + s.to_tokens(tokens); + } + syn::Item::Impl(mut i) => { + (&mut i).macro_parse(program, ()); + i.to_tokens(tokens); + } + syn::Item::ForeignMod(mut f) => { + let opts = opts.unwrap_or_else(|| BindgenAttrs::find(&mut f.attrs)); + f.macro_parse(program, opts); + } + syn::Item::Enum(e) => { + e.to_tokens(tokens); + e.macro_parse(program, ()); + } + _ => panic!( + "#[wasm_bindgen] can only be applied to a function, \ + struct, enum, impl, or extern block" + ), + } + } +} + +impl<'a> MacroParse<()> for &'a mut syn::ItemImpl { + fn macro_parse(self, program: &mut ast::Program, (): ()) { + if self.defaultness.is_some() { + panic!("default impls are not supported"); + } + if self.unsafety.is_some() { + panic!("unsafe impls are not supported"); + } + if self.trait_.is_some() { + panic!("trait impls are not supported"); + } + if self.generics.params.len() > 0 { + panic!("generic impls aren't supported"); + } + let name = match *self.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"), + }, + _ => panic!("unsupported self type in impl"), + }; + for item in self.items.iter_mut() { + (&name, item).macro_parse(program, ()) + } + } +} + +impl<'a, 'b> MacroParse<()> for (&'a Ident, &'b mut syn::ImplItem) { + fn macro_parse(self, program: &mut ast::Program, (): ()) { + let (class, item) = self; + 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); + let comments = extract_doc_comments(&method.attrs); + let is_constructor = opts.constructor(); + let constructor = if is_constructor { + Some(method.sig.ident.to_string()) + } else { + None + }; + + let (function, method_self) = function_from_decl( + &method.sig.ident, + Box::new(method.sig.decl.clone()), + method.attrs.clone(), + method.vis.clone(), + true, + ); + + program.exports.push(ast::Export { + class: Some(class.clone()), + method_self, + constructor, + function, + comments, + }); + } +} + +impl MacroParse<()> for syn::ItemEnum { + fn macro_parse(self, program: &mut ast::Program, (): ()) { + match self.vis { + syn::Visibility::Public(_) => {} + _ => panic!("only public enums are allowed"), + } + + let variants = self + .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() > ::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"), + }; + + ast::Variant { + name: v.ident.clone(), + value, + } + }) + .collect(); + let comments = extract_doc_comments(&self.attrs); + program.enums.push(ast::Enum { + name: self.ident, + variants, + comments, + }); + } +} + +impl MacroParse for syn::ItemForeignMod { + fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) { + match self.abi.name { + Some(ref l) if l.value() == "C" => {} + None => {} + _ => panic!("only foreign mods with the `C` ABI are allowed"), + } + for mut item in self.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 version = item_opts + .version() + .or(opts.version()) + .map(|s| s.to_string()); + let js_namespace = item_opts.js_namespace().or(opts.js_namespace()).cloned(); + let mut kind = match item { + syn::ForeignItem::Fn(f) => f.convert(item_opts), + syn::ForeignItem::Type(t) => t.convert(()), + syn::ForeignItem::Static(s) => s.convert(item_opts), + _ => panic!("only foreign functions/types allowed for now"), + }; + + program.imports.push(ast::Import { + module, + version, + js_namespace, + kind, + }); + } + } +} + +fn extract_first_ty_param(ty: Option<&syn::Type>) -> Option> { + 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 replace_self(name: &Ident, item: &mut syn::ImplItem) { + struct Walk<'a>(&'a Ident); + + impl<'a> syn::visit_mut::VisitMut for Walk<'a> { + fn visit_ident_mut(&mut self, i: &mut Ident) { + if i == "Self" { + *i = self.0.clone(); + } + } + } + + syn::visit_mut::VisitMut::visit_impl_item_mut(&mut Walk(name), item); +} + +/// Extract the documentation comments from a Vec of attributes +fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec { + attrs + .iter() + .filter_map(|a| { + // if the path segments include an ident of "doc" we know this + // this is a doc comment + if a.path.segments.iter().any(|s| s.ident.to_string() == "doc") { + Some( + // We want to filter out any Puncts so just grab the Literals + a.tts.clone().into_iter().filter_map(|t| match t { + TokenTree::Literal(lit) => { + // this will always return the quoted string, we deal with + // that in the cli when we read in the comments + Some(lit.to_string()) + }, + _ => None, + }) + ) + } else { + None + } + }) + //Fold up the [[String]] iter we created into Vec + .fold(vec![], |mut acc, a| {acc.extend(a); acc}) +} + +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 extract_path_ident(path: &syn::Path) -> Option { + if path.leading_colon.is_some() { + return None; + } + if path.segments.len() != 1 { + return None; + } + match path.segments.first().unwrap().value().arguments { + syn::PathArguments::None => {} + _ => return None, + } + path.segments.first().map(|v| v.value().ident.clone()) +} diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 66f624d7..08285092 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate serde_derive; -pub const SCHEMA_VERSION: &str = "5"; +pub const SCHEMA_VERSION: &str = "6"; #[derive(Deserialize)] pub struct ProgramOnlySchema { @@ -48,15 +48,25 @@ pub struct ImportFunction { pub struct MethodData { pub class: String, pub kind: MethodKind, - pub getter: Option, - pub setter: Option, } #[derive(Deserialize, Serialize)] pub enum MethodKind { - Normal, Constructor, - Static, + Operation(Operation), +} + +#[derive(Deserialize, Serialize)] +pub struct Operation { + pub is_static: bool, + pub kind: OperationKind, +} + +#[derive(Deserialize, Serialize)] +pub enum OperationKind { + Regular, + Getter(String), + Setter(String), } #[derive(Deserialize, Serialize)] diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index 8e3b4b26..7b5e24af 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -129,7 +129,7 @@ impl WebidlParse<()> for webidl::ast::Interface { impl WebidlParse<()> for webidl::ast::Typedef { fn webidl_parse(&self, program: &mut backend::ast::Program, _: ()) -> Result<()> { if util::is_chrome_only(&self.extended_attributes) { - return Ok(()) + return Ok(()); } let dest = rust_ident(&self.name); @@ -159,7 +159,7 @@ impl WebidlParse<()> for webidl::ast::Typedef { impl WebidlParse<()> for webidl::ast::NonPartialInterface { fn webidl_parse(&self, program: &mut backend::ast::Program, _: ()) -> Result<()> { if util::is_chrome_only(&self.extended_attributes) { - return Ok(()) + return Ok(()); } program.imports.push(backend::ast::Import { @@ -205,9 +205,8 @@ impl<'a> WebidlParse<&'a webidl::ast::NonPartialInterface> for webidl::ast::Exte arguments .iter() .map(|arg| (&*arg.name, &*arg.type_, arg.variadic)), - kind, Some(self_ty), - vec![backend::ast::BindgenAttr::Constructor], + kind, ).map(|function| { program.imports.push(backend::ast::Import { module: None, @@ -303,24 +302,16 @@ impl<'a> WebidlParse<&'a str> for webidl::ast::Operation { impl<'a> WebidlParse<&'a str> for webidl::ast::RegularAttribute { fn webidl_parse(&self, program: &mut backend::ast::Program, self_name: &'a str) -> Result<()> { if util::is_chrome_only(&self.extended_attributes) { - return Ok(()) + return Ok(()); } - create_getter( - &self.name, - &self.type_, - self_name, - backend::ast::MethodKind::Normal, - ).map(wrap_import_function) + create_getter(&self.name, &self.type_, self_name, false) + .map(wrap_import_function) .map(|import| program.imports.push(import)); if !self.read_only { - create_setter( - &self.name, - &self.type_, - self_name, - backend::ast::MethodKind::Normal, - ).map(wrap_import_function) + create_setter(&self.name, &self.type_, self_name, false) + .map(wrap_import_function) .map(|import| program.imports.push(import)); } @@ -331,24 +322,16 @@ impl<'a> WebidlParse<&'a str> for webidl::ast::RegularAttribute { impl<'a> WebidlParse<&'a str> for webidl::ast::StaticAttribute { fn webidl_parse(&self, program: &mut backend::ast::Program, self_name: &'a str) -> Result<()> { if util::is_chrome_only(&self.extended_attributes) { - return Ok(()) + return Ok(()); } - create_getter( - &self.name, - &self.type_, - self_name, - backend::ast::MethodKind::Static, - ).map(wrap_import_function) + create_getter(&self.name, &self.type_, self_name, true) + .map(wrap_import_function) .map(|import| program.imports.push(import)); if !self.read_only { - create_setter( - &self.name, - &self.type_, - self_name, - backend::ast::MethodKind::Static, - ).map(wrap_import_function) + create_setter(&self.name, &self.type_, self_name, true) + .map(wrap_import_function) .map(|import| program.imports.push(import)); } @@ -359,7 +342,7 @@ impl<'a> WebidlParse<&'a str> for webidl::ast::StaticAttribute { impl<'a> WebidlParse<&'a str> for webidl::ast::RegularOperation { fn webidl_parse(&self, program: &mut backend::ast::Program, self_name: &'a str) -> Result<()> { if util::is_chrome_only(&self.extended_attributes) { - return Ok(()) + return Ok(()); } create_basic_method( @@ -367,7 +350,7 @@ impl<'a> WebidlParse<&'a str> for webidl::ast::RegularOperation { self.name.as_ref(), &self.return_type, self_name, - backend::ast::MethodKind::Normal, + false, ).map(wrap_import_function) .map(|import| program.imports.push(import)); @@ -378,7 +361,7 @@ impl<'a> WebidlParse<&'a str> for webidl::ast::RegularOperation { impl<'a> WebidlParse<&'a str> for webidl::ast::StaticOperation { fn webidl_parse(&self, program: &mut backend::ast::Program, self_name: &'a str) -> Result<()> { if util::is_chrome_only(&self.extended_attributes) { - return Ok(()) + return Ok(()); } create_basic_method( @@ -386,7 +369,7 @@ impl<'a> WebidlParse<&'a str> for webidl::ast::StaticOperation { self.name.as_ref(), &self.return_type, self_name, - backend::ast::MethodKind::Static, + true, ).map(wrap_import_function) .map(|import| program.imports.push(import)); diff --git a/crates/webidl/src/util.rs b/crates/webidl/src/util.rs index aeeb29f3..b79445db 100644 --- a/crates/webidl/src/util.rs +++ b/crates/webidl/src/util.rs @@ -114,7 +114,10 @@ where let len = estimate.1.unwrap_or(estimate.0); let mut res = if let backend::ast::ImportFunctionKind::Method { ty, - kind: backend::ast::MethodKind::Normal, + kind: + backend::ast::MethodKind::Operation(backend::ast::Operation { + is_static: false, .. + }), .. } = kind { @@ -146,9 +149,8 @@ where pub fn create_function<'a, I>( name: &str, arguments: I, - kind: backend::ast::ImportFunctionKind, ret: Option, - mut attrs: Vec, + kind: backend::ast::ImportFunctionKind, ) -> Option where I: Iterator, @@ -160,12 +162,6 @@ where let js_ret = ret.clone(); - if let backend::ast::ImportFunctionKind::Method { .. } = kind { - attrs.push(backend::ast::BindgenAttr::Method); - } - - let opts = backend::ast::BindgenAttrs { attrs }; - let shim = { let ns = match kind { backend::ast::ImportFunctionKind::Normal => "", @@ -180,7 +176,6 @@ where name, arguments, ret, - opts, rust_attrs: vec![], rust_vis: syn::Visibility::Public(syn::VisPublic { pub_token: Default::default(), @@ -188,6 +183,8 @@ where }, rust_name, js_ret, + catch: false, + structural: false, kind, shim, }) @@ -198,7 +195,7 @@ pub fn create_basic_method( name: Option<&String>, return_type: &webidl::ast::ReturnType, self_name: &str, - kind: backend::ast::MethodKind, + is_static: bool, ) -> Option { let name = match name { None => { @@ -211,7 +208,10 @@ pub fn create_basic_method( let kind = backend::ast::ImportFunctionKind::Method { class: self_name.to_string(), ty: ident_ty(rust_ident(self_name)), - kind, + kind: backend::ast::MethodKind::Operation(backend::ast::Operation { + is_static, + kind: backend::ast::OperationKind::Regular, + }), }; let ret = match return_type { @@ -231,9 +231,8 @@ pub fn create_basic_method( arguments .iter() .map(|arg| (&*arg.name, &*arg.type_, arg.variadic)), - kind, ret, - Vec::new(), + kind, ) } @@ -241,7 +240,7 @@ pub fn create_getter( name: &str, ty: &webidl::ast::Type, self_name: &str, - kind: backend::ast::MethodKind, + is_static: bool, ) -> Option { let ret = match webidl_ty_to_syn_ty(ty, TypePosition::Return) { None => { @@ -254,36 +253,35 @@ pub fn create_getter( let kind = backend::ast::ImportFunctionKind::Method { class: self_name.to_string(), ty: ident_ty(rust_ident(self_name)), - kind, + kind: backend::ast::MethodKind::Operation(backend::ast::Operation { + is_static, + kind: backend::ast::OperationKind::Getter(Some(raw_ident(name))), + }), }; - create_function( - name, - iter::empty(), - kind, - ret, - vec![backend::ast::BindgenAttr::Getter(Some(raw_ident(name)))], - ) + create_function(name, iter::empty(), ret, kind) } pub fn create_setter( name: &str, ty: &webidl::ast::Type, self_name: &str, - kind: backend::ast::MethodKind, + is_static: bool, ) -> Option { let kind = backend::ast::ImportFunctionKind::Method { class: self_name.to_string(), ty: ident_ty(rust_ident(self_name)), - kind, + kind: backend::ast::MethodKind::Operation(backend::ast::Operation { + is_static, + kind: backend::ast::OperationKind::Setter(Some(raw_ident(name))), + }), }; create_function( &format!("set_{}", name), iter::once((name, ty, false)), - kind, None, - vec![backend::ast::BindgenAttr::Setter(Some(raw_ident(name)))], + kind, ) } @@ -294,24 +292,26 @@ pub fn is_chrome_only(ext_attrs: &[Box]) -> bool { ExtendedAttribute::ArgumentList(al) => { println!("ArgumentList"); al.name == "ChromeOnly" - }, + } ExtendedAttribute::Identifier(i) => { println!("Identifier"); i.lhs == "ChromeOnly" - }, + } ExtendedAttribute::IdentifierList(il) => { println!("IdentifierList"); il.lhs == "ChromeOnly" - }, + } ExtendedAttribute::NamedArgumentList(nal) => { println!("NamedArgumentList"); nal.lhs_name == "ChromeOnly" - }, - ExtendedAttribute::NoArguments(webidl::ast::Other::Identifier(name)) => name == "ChromeOnly", + } + ExtendedAttribute::NoArguments(webidl::ast::Other::Identifier(name)) => { + name == "ChromeOnly" + } ExtendedAttribute::NoArguments(_na) => { println!("NoArguments"); false } }; }) -} \ No newline at end of file +}