Merge pull request #547 from derekdreery/extra_docs

Extra docs
This commit is contained in:
Nick Fitzgerald 2018-07-25 12:04:29 -07:00 committed by GitHub
commit dbb498174e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 126 additions and 9 deletions

View File

@ -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<Export>,
/// js -> rust interfaces
pub imports: Vec<Import>,
/// rust enums
pub enums: Vec<Enum>,
/// rust structs
pub structs: Vec<Struct>,
/// rust type aliases
pub type_aliases: Vec<TypeAlias>,
/// rust consts
pub consts: Vec<Const>,
}
/// 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<Ident>,
/// The type of `self` (either `self`, `&self`, or `&mut self`)
pub method_self: Option<MethodSelf>,
/// 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<String>,
/// The rust function
pub function: Function,
/// Comments extracted from the rust source.
pub comments: Vec<String>,
/// 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_`");

View File

@ -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<BindgenAttr>,
}
impl BindgenAttrs {
/// Find and parse the wasm_bindgen attributes.
fn find(attrs: &mut Vec<syn::Attribute>) -> 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<Option<Ident>> {
self.attrs
.iter()
@ -105,6 +116,7 @@ impl BindgenAttrs {
.next()
}
/// Get the first setter attribute
fn setter(&self) -> Option<Option<Ident>> {
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<Ctx> {
/// 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<BindgenAttrs> 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<syn::FnDecl>,
@ -556,6 +584,10 @@ fn function_from_decl(
}
pub(crate) trait MacroParse<Ctx> {
/// 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<BindgenAttrs> for syn::ItemForeignMod {
}
}
fn extract_first_ty_param(ty: Option<&syn::Type>) -> Option<Option<syn::Type>> {
/// Get the first type parameter of a generic type, errors on incorrect input.
fn extract_first_ty_param(ty: Option<&syn::Type>) -> Result<Option<syn::Type>, ()> {
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<String> {
.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<Ident> {
if path.leading_colon.is_some() {
return None;

View File

@ -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<String>,
pub(crate) dictionaries: BTreeSet<String>,
pub(crate) enums: BTreeSet<String>,
/// The mixins, mapping their name to the webidl ast node for the mixin.
pub(crate) mixins: BTreeMap<String, MixinData<'a>>,
}
/// 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<()>;
}

View File

@ -78,6 +78,7 @@ pub fn compile(webidl_source: &str) -> Result<String> {
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<Ctx> {
/// Parse `self` into wasm-bindgen AST, and insert it into `program`.
fn webidl_parse(
&self,
program: &mut backend::ast::Program,

View File

@ -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<T, ::wasm_bindgen::JsValue>`.
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<T>`.
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<syn::Type> {
// Array type is borrowed for arguments (`&[T]`) and owned for return value (`Vec<T>`).
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<ExtendedAttribute>], 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<ExtendedAttribute>]) -> 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<ExtendedAttribute>]) -> bool {
has_named_attribute(ext_attrs, "NoInterfaceObject")
}
/// Whether a webidl object is marked as structural.
pub fn is_structural(attrs: &[Box<ExtendedAttribute>]) -> bool {
attrs.iter().any(|attr| match &**attr {
ExtendedAttribute::NoArguments(webidl::ast::Other::Identifier(name)) => {
@ -469,6 +494,7 @@ pub fn is_structural(attrs: &[Box<ExtendedAttribute>]) -> bool {
})
}
/// Whether a webidl object is marked as throwing.
pub fn throws(attrs: &[Box<ExtendedAttribute>]) -> 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<ExtendedAttribute>]) -> bool {
})
}
/// Create a syn `pub` token
pub fn public() -> syn::Visibility {
syn::Visibility::Public(syn::VisPublic {
pub_token: Default::default(),