diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 34cdd9809..d336b6bf0 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -2,31 +2,54 @@ use proc_macro2::{Ident, Span}; use shared; use syn; +/// An abstract syntax tree representing a rust program. Contains +/// extra information for joining up this rust code with javascript. #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq))] #[derive(Default)] pub struct Program { + /// rust -> js interfaces pub exports: Vec, + /// js -> rust interfaces pub imports: Vec, + /// rust enums pub enums: Vec, + /// rust structs pub structs: Vec, + /// rust type aliases pub type_aliases: Vec, + /// rust consts pub consts: Vec, } +/// A rust to js interface. Allows interaction with rust objects/functions +/// from javascript. #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] pub struct Export { + /// The javascript class name. pub class: Option, + /// The type of `self` (either `self`, `&self`, or `&mut self`) pub method_self: Option, + /// The name of the constructor function (e.g. new). + /// + /// This allows javascript to expose an `Object` interface, where calling + /// the constructor maps correctly to rust. pub constructor: Option, + /// The rust function pub function: Function, + /// Comments extracted from the rust source. pub comments: Vec, + /// The name of the rust function/method on the rust side. pub rust_name: Ident, } +/// The 3 types variations of `self`. #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] pub enum MethodSelf { + /// `self` ByValue, + /// `&mut self` RefMutable, + /// `&self` RefShared, } @@ -219,6 +242,8 @@ impl Function { } impl Export { + /// Mangles a rust -> javascript export, so that the created Ident will be unique over function + /// name and class name, if the function belongs to a javascript class. pub(crate) fn rust_symbol(&self) -> Ident { let mut generated_name = String::from("__wasm_bindgen_generated"); if let Some(class) = &self.class { @@ -230,6 +255,9 @@ impl Export { Ident::new(&generated_name, Span::call_site()) } + /// This is the name of the shim function that gets exported and takes the raw + /// ABI form of its arguments and converts them back into their normal, + /// "high level" form before calling the actual function. pub(crate) fn export_name(&self) -> String { let fn_name = self.function.name.to_string(); match &self.class { @@ -310,6 +338,7 @@ impl Import { } impl ImportKind { + /// Whether this type can be inside an `impl` block. pub fn fits_on_impl(&self) -> bool { match *self { ImportKind::Function(_) => true, @@ -330,10 +359,14 @@ impl ImportKind { } impl ImportFunction { + /// If the rust object has a `fn xxx(&self) -> MyType` method, get the name for a getter in + /// javascript (in this case `xxx`, so you can write `val = obj.xxx`) fn infer_getter_property(&self) -> String { self.function.name.to_string() } + /// If the rust object has a `fn set_xxx(&mut self, MyType)` style method, get the name + /// for a setter in javascript (in this case `xxx`, so you can write `obj.xxx = val`) fn infer_setter_property(&self) -> String { let name = self.function.name.to_string(); assert!(name.starts_with("set_"), "setters must start with `set_`"); diff --git a/crates/macro/src/parser.rs b/crates/macro/src/parser.rs index 6955174e4..cb65fd2a3 100644 --- a/crates/macro/src/parser.rs +++ b/crates/macro/src/parser.rs @@ -5,13 +5,16 @@ use quote::ToTokens; use shared; use syn; +/// Parsed attributes from a `#[wasm_bindgen(..)]`. #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[derive(Default)] pub struct BindgenAttrs { + /// List of parsed attributes pub attrs: Vec, } impl BindgenAttrs { + /// Find and parse the wasm_bindgen attributes. fn find(attrs: &mut Vec) -> BindgenAttrs { let pos = attrs .iter() @@ -34,6 +37,7 @@ impl BindgenAttrs { syn::parse(tt.into()).expect("malformed #[wasm_bindgen] attribute") } + /// Get the first module attribute fn module(&self) -> Option<&str> { self.attrs .iter() @@ -44,6 +48,7 @@ impl BindgenAttrs { .next() } + /// Get the first version attribute fn version(&self) -> Option<&str> { self.attrs .iter() @@ -54,6 +59,7 @@ impl BindgenAttrs { .next() } + /// Whether the catch attribute is present fn catch(&self) -> bool { self.attrs.iter().any(|a| match a { BindgenAttr::Catch => true, @@ -61,6 +67,7 @@ impl BindgenAttrs { }) } + /// Whether the constructor attribute is present fn constructor(&self) -> bool { self.attrs.iter().any(|a| match a { BindgenAttr::Constructor => true, @@ -68,6 +75,7 @@ impl BindgenAttrs { }) } + /// Get the first static_method_of attribute fn static_method_of(&self) -> Option<&Ident> { self.attrs .iter() @@ -78,6 +86,7 @@ impl BindgenAttrs { .next() } + /// Whether the method attributes is present fn method(&self) -> bool { self.attrs.iter().any(|a| match a { BindgenAttr::Method => true, @@ -85,6 +94,7 @@ impl BindgenAttrs { }) } + /// Get the first js_namespace attribute fn js_namespace(&self) -> Option<&Ident> { self.attrs .iter() @@ -95,6 +105,7 @@ impl BindgenAttrs { .next() } + /// Get the first getter attribute fn getter(&self) -> Option> { self.attrs .iter() @@ -105,6 +116,7 @@ impl BindgenAttrs { .next() } + /// Get the first setter attribute fn setter(&self) -> Option> { self.attrs .iter() @@ -115,6 +127,7 @@ impl BindgenAttrs { .next() } + /// Whether the structural attributes is present fn structural(&self) -> bool { self.attrs.iter().any(|a| match *a { BindgenAttr::Structural => true, @@ -122,6 +135,7 @@ impl BindgenAttrs { }) } + /// Whether the readonly attributes is present fn readonly(&self) -> bool { self.attrs.iter().any(|a| match *a { BindgenAttr::Readonly => true, @@ -129,6 +143,7 @@ impl BindgenAttrs { }) } + /// Get the first js_name attribute fn js_name(&self) -> Option<&Ident> { self.attrs .iter() @@ -139,6 +154,7 @@ impl BindgenAttrs { .next() } + /// Get the first js_name attribute fn js_class(&self) -> Option<&str> { self.attrs .iter() @@ -165,6 +181,7 @@ impl syn::synom::Synom for BindgenAttrs { )); } +/// The possible attributes in the `#[wasm_bindgen]`. #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] pub enum BindgenAttr { Catch, @@ -258,6 +275,7 @@ impl syn::synom::Synom for BindgenAttr { )); } +/// Consumes a `Ident` with the given name fn term<'a>(cursor: syn::buffer::Cursor<'a>, name: &str) -> syn::synom::PResult<'a, ()> { if let Some((ident, next)) = cursor.ident() { if ident == name { @@ -267,6 +285,7 @@ fn term<'a>(cursor: syn::buffer::Cursor<'a>, name: &str) -> syn::synom::PResult< syn::parse_error() } +/// Consumes a `Ident` and returns it. fn term2ident<'a>(cursor: syn::buffer::Cursor<'a>) -> syn::synom::PResult<'a, Ident> { match cursor.ident() { Some(pair) => Ok(pair), @@ -274,8 +293,16 @@ fn term2ident<'a>(cursor: syn::buffer::Cursor<'a>) -> syn::synom::PResult<'a, Id } } +/// Conversion trait with context. +/// +/// Used to convert syn tokens into an AST, that we can then use to generate glue code. The context +/// (`Ctx`) is used to pass in the attributes from the `#[wasm_bindgen]`, if needed. trait ConvertToAst { + /// What we are converting to. type Target; + /// Convert into our target. + /// + /// Since this is used in a procedural macro, use panic to fail. fn convert(self, context: Ctx) -> Self::Target; } @@ -497,6 +524,7 @@ impl ConvertToAst for syn::ItemFn { } } +/// Construct a function (and gets the self type if appropriate) for our AST from a syn function. fn function_from_decl( name: &Ident, mut decl: Box, @@ -556,6 +584,10 @@ fn function_from_decl( } pub(crate) trait MacroParse { + /// Parse the contents of an object into our AST, with a context if necessary. + /// + /// The context is used to have access to the attributes on `#[wasm_bindgen]`, and to allow + /// writing to the output `TokenStream`. fn macro_parse(self, program: &mut ast::Program, context: Ctx); } @@ -785,34 +817,36 @@ impl MacroParse for syn::ItemForeignMod { } } -fn extract_first_ty_param(ty: Option<&syn::Type>) -> Option> { +/// Get the first type parameter of a generic type, errors on incorrect input. +fn extract_first_ty_param(ty: Option<&syn::Type>) -> Result, ()> { let t = match ty { Some(t) => t, - None => return Some(None), + None => return Ok(None), }; let path = match *t { syn::Type::Path(syn::TypePath { qself: None, ref path, }) => path, - _ => return None, + _ => return Err(()), }; - let seg = path.segments.last()?.into_value(); + let seg = path.segments.last().ok_or(())?.into_value(); let generics = match seg.arguments { syn::PathArguments::AngleBracketed(ref t) => t, - _ => return None, + _ => return Err(()), }; - let ty = match *generics.args.first()?.into_value() { + let ty = match *generics.args.first().ok_or(())?.into_value() { syn::GenericArgument::Type(ref t) => t, - _ => return None, + _ => return Err(()), }; match *ty { - syn::Type::Tuple(ref t) if t.elems.len() == 0 => return Some(None), + syn::Type::Tuple(ref t) if t.elems.len() == 0 => return Ok(None), _ => {} } - Some(Some(ty.clone())) + Ok(Some(ty.clone())) } +/// Replace `Self` with the given name in `item`. fn replace_self(name: &Ident, item: &mut syn::ImplItem) { struct Walk<'a>(&'a Ident); @@ -854,6 +888,7 @@ fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec { .fold(vec![], |mut acc, a| {acc.extend(a); acc}) } +/// Check there are no lifetimes on the function. fn assert_no_lifetimes(decl: &mut syn::FnDecl) { struct Walk; @@ -869,6 +904,7 @@ fn assert_no_lifetimes(decl: &mut syn::FnDecl) { syn::visit_mut::VisitMut::visit_fn_decl_mut(&mut Walk, decl); } +/// If the path is a single ident, return it. fn extract_path_ident(path: &syn::Path) -> Option { if path.leading_colon.is_some() { return None; diff --git a/crates/webidl/src/first_pass.rs b/crates/webidl/src/first_pass.rs index a4b063c8b..a2fd089a0 100644 --- a/crates/webidl/src/first_pass.rs +++ b/crates/webidl/src/first_pass.rs @@ -1,3 +1,12 @@ +//! Because some WebIDL constructs are defined in multiple places +//! (keyword `partial` is used to add to an existing construct), +//! We need to first walk the webidl to collect all non-partial +//! constructs so that we have containers in which to put the +//! partial ones. +//! +//! Only `interface`s, `dictionary`s, `enum`s and `mixin`s can +//! be partial. + use std::{ collections::{BTreeMap, BTreeSet}, mem, }; @@ -6,21 +15,30 @@ use webidl; use super::Result; +/// Collection of constructs that may use partial. #[derive(Default)] pub(crate) struct FirstPassRecord<'a> { pub(crate) interfaces: BTreeSet, pub(crate) dictionaries: BTreeSet, pub(crate) enums: BTreeSet, + /// The mixins, mapping their name to the webidl ast node for the mixin. pub(crate) mixins: BTreeMap>, } +/// We need to collect mixin data during the first pass, to be used later. #[derive(Default)] pub(crate) struct MixinData<'a> { + /// The non partial mixin, if present. If there is more than one, we are + /// parsing is a malformed WebIDL file, but the parser will recover by + /// using the last parsed mixin. pub(crate) non_partial: Option<&'a webidl::ast::NonPartialMixin>, + /// 0 or more partial mixins. pub(crate) partials: Vec<&'a webidl::ast::PartialMixin>, } +/// Implemented on an AST node to populate the `FirstPassRecord` struct. pub(crate) trait FirstPass { + /// Populate `record` with any constructs in `self`. fn first_pass<'a>(&'a self, record: &mut FirstPassRecord<'a>) -> Result<()>; } diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index d3de58a6d..129fbe43c 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -78,6 +78,7 @@ pub fn compile(webidl_source: &str) -> Result { Ok(compile_ast(ast)) } +/// Run codegen on the AST to generate rust code. fn compile_ast(mut ast: backend::ast::Program) -> String { let mut defined = BTreeSet::from_iter( vec![ @@ -96,7 +97,9 @@ fn compile_ast(mut ast: backend::ast::Program) -> String { tokens.to_string() } +/// The main trait for parsing WebIDL AST into wasm-bindgen AST. trait WebidlParse { + /// Parse `self` into wasm-bindgen AST, and insert it into `program`. fn webidl_parse( &self, program: &mut backend::ast::Program, diff --git a/crates/webidl/src/util.rs b/crates/webidl/src/util.rs index a2293c86b..6d276af10 100644 --- a/crates/webidl/src/util.rs +++ b/crates/webidl/src/util.rs @@ -10,6 +10,7 @@ use webidl::ast::ExtendedAttribute; use first_pass::FirstPassRecord; +/// Take a type and create an immutable shared reference to that type. fn shared_ref(ty: syn::Type) -> syn::Type { syn::TypeReference { and_token: Default::default(), @@ -19,6 +20,7 @@ fn shared_ref(ty: syn::Type) -> syn::Type { }.into() } +/// For a webidl const type node, get the corresponding syn type node. pub fn webidl_const_ty_to_syn_ty(ty: &webidl::ast::ConstType) -> syn::Type { use webidl::ast::ConstType::*; @@ -39,6 +41,7 @@ pub fn webidl_const_ty_to_syn_ty(ty: &webidl::ast::ConstType) -> syn::Type { } } +/// Map a webidl const value to the correct wasm-bindgen const value pub fn webidl_const_v_to_backend_const_v(v: &webidl::ast::ConstValue) -> backend::ast::ConstValue { match *v { webidl::ast::ConstValue::BooleanLiteral(b) => backend::ast::ConstValue::BooleanLiteral(b), @@ -49,6 +52,7 @@ pub fn webidl_const_v_to_backend_const_v(v: &webidl::ast::ConstValue) -> backend } } +/// From `ident` and `Ty`, create `ident: Ty` for use in e.g. `fn(ident: Ty)`. fn simple_fn_arg(ident: Ident, ty: syn::Type) -> syn::ArgCaptured { syn::ArgCaptured { pat: syn::Pat::Ident(syn::PatIdent { @@ -62,6 +66,7 @@ fn simple_fn_arg(ident: Ident, ty: syn::Type) -> syn::ArgCaptured { } } +/// Create `()`. fn unit_ty() -> syn::Type { syn::Type::Tuple(syn::TypeTuple { paren_token: Default::default(), @@ -69,6 +74,7 @@ fn unit_ty() -> syn::Type { }) } +/// From `T` create `Result`. fn result_ty(t: syn::Type) -> syn::Type { let js_value = leading_colon_path_ty(vec![rust_ident("wasm_bindgen"), rust_ident("JsValue")]); @@ -89,6 +95,7 @@ fn result_ty(t: syn::Type) -> syn::Type { ty.into() } +/// From `T` create `[T]`. fn slice_ty(t: syn::Type) -> syn::Type { syn::TypeSlice { bracket_token: Default::default(), @@ -96,6 +103,7 @@ fn slice_ty(t: syn::Type) -> syn::Type { }.into() } +/// From `T` create `Vec`. fn vec_ty(t: syn::Type) -> syn::Type { let arguments = syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { colon2_token: None, @@ -113,6 +121,7 @@ fn vec_ty(t: syn::Type) -> syn::Type { ty.into() } +/// Possible positions for a type in a function signature. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum TypePosition { Argument, @@ -120,11 +129,14 @@ pub enum TypePosition { } impl<'a> FirstPassRecord<'a> { + /// Use information from the first pass to work out the correct Rust type to use for + /// a given WebIDL type. pub fn webidl_ty_to_syn_ty( &self, ty: &webidl::ast::Type, pos: TypePosition, ) -> Option { + // Array type is borrowed for arguments (`&[T]`) and owned for return value (`Vec`). let array = |base_ty: &str| { match pos { TypePosition::Argument => { @@ -220,6 +232,8 @@ impl<'a> FirstPassRecord<'a> { return None; } }; + + // Map nullable to an option. if ty.nullable { let arguments = syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { colon2_token: None, @@ -240,6 +254,10 @@ impl<'a> FirstPassRecord<'a> { } } + /// Use the first pass to convert webidl function arguments to rust arguments. + /// + /// `kind` is whether the function is a method, in which case we would need a `self` + /// parameter. fn webidl_arguments_to_syn_arg_captured<'b, I>( &self, arguments: I, @@ -284,6 +302,7 @@ impl<'a> FirstPassRecord<'a> { Some(res) } + /// Create a wasm-bindgen function, if possible. pub fn create_function<'b, I>( &self, name: &str, @@ -333,6 +352,7 @@ impl<'a> FirstPassRecord<'a> { }) } + /// Create a wasm-bindgen method, if possible. pub fn create_basic_method( &self, arguments: &[webidl::ast::Argument], @@ -384,6 +404,7 @@ impl<'a> FirstPassRecord<'a> { ) } + /// Create a wasm-bindgen getter method, if possible. pub fn create_getter( &self, name: &str, @@ -413,6 +434,7 @@ impl<'a> FirstPassRecord<'a> { self.create_function(name, iter::empty(), ret, kind, is_structural, catch) } + /// Create a wasm-bindgen setter method, if possible. pub fn create_setter( &self, name: &str, @@ -442,6 +464,7 @@ impl<'a> FirstPassRecord<'a> { } } +/// Search for an attribute by name in some webidl object's attributes. fn has_named_attribute(ext_attrs: &[Box], attribute: &str) -> bool { ext_attrs.iter().any(|attr| match &**attr { ExtendedAttribute::NoArguments(webidl::ast::Other::Identifier(name)) => { @@ -456,10 +479,12 @@ pub fn is_chrome_only(ext_attrs: &[Box]) -> bool { has_named_attribute(ext_attrs, "ChromeOnly") } +/// Whether a webidl object is marked as a no interface object. pub fn is_no_interface_object(ext_attrs: &[Box]) -> bool { has_named_attribute(ext_attrs, "NoInterfaceObject") } +/// Whether a webidl object is marked as structural. pub fn is_structural(attrs: &[Box]) -> bool { attrs.iter().any(|attr| match &**attr { ExtendedAttribute::NoArguments(webidl::ast::Other::Identifier(name)) => { @@ -469,6 +494,7 @@ pub fn is_structural(attrs: &[Box]) -> bool { }) } +/// Whether a webidl object is marked as throwing. pub fn throws(attrs: &[Box]) -> bool { attrs.iter().any(|attr| match &**attr { ExtendedAttribute::NoArguments(webidl::ast::Other::Identifier(name)) => name == "Throws", @@ -476,6 +502,7 @@ pub fn throws(attrs: &[Box]) -> bool { }) } +/// Create a syn `pub` token pub fn public() -> syn::Visibility { syn::Visibility::Public(syn::VisPublic { pub_token: Default::default(),