1119 lines
36 KiB
Rust
Raw Normal View History

use backend::ast;
use backend::util::{ident_ty, ShortHash};
use backend::Diagnostic;
use proc_macro2::{Delimiter, Ident, Span, TokenStream, TokenTree};
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>) -> Result<BindgenAttrs, Diagnostic> {
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 Ok(BindgenAttrs::default()),
};
let attr = attrs.remove(pos);
let mut tts = attr.tts.clone().into_iter();
let group = match tts.next() {
Some(TokenTree::Group(d)) => d,
Some(_) => bail_span!(attr, "malformed #[wasm_bindgen] attribute"),
None => return Ok(BindgenAttrs::default()),
};
if tts.next().is_some() {
bail_span!(attr, "malformed #[wasm_bindgen] attribute");
}
if group.delimiter() != Delimiter::Parenthesis {
bail_span!(attr, "malformed #[wasm_bindgen] attribute");
}
super::syn_parse(group.stream(), "#[wasm_bindgen] attribute options")
}
/// Get the first module attribute
fn module(&self) -> Option<&str> {
self.attrs
.iter()
.filter_map(|a| match a {
BindgenAttr::Module(s) => Some(&s[..]),
_ => None,
}).next()
}
/// Whether the catch attribute is present
fn catch(&self) -> bool {
self.attrs.iter().any(|a| match a {
BindgenAttr::Catch => true,
_ => false,
})
}
/// Whether the constructor attribute is present
fn constructor(&self) -> bool {
self.attrs.iter().any(|a| match a {
BindgenAttr::Constructor => true,
_ => false,
})
}
/// Get the first static_method_of attribute
fn static_method_of(&self) -> Option<&Ident> {
self.attrs
.iter()
.filter_map(|a| match a {
BindgenAttr::StaticMethodOf(c) => Some(c),
_ => None,
}).next()
}
/// Whether the method attributes is present
fn method(&self) -> bool {
self.attrs.iter().any(|a| match a {
BindgenAttr::Method => true,
_ => false,
})
}
/// Get the first js_namespace attribute
fn js_namespace(&self) -> Option<&Ident> {
self.attrs
.iter()
.filter_map(|a| match a {
BindgenAttr::JsNamespace(s) => Some(s),
_ => None,
}).next()
}
/// Get the first getter attribute
fn getter(&self) -> Option<Option<Ident>> {
self.attrs
.iter()
.filter_map(|a| match a {
BindgenAttr::Getter(g) => Some(g.clone()),
_ => None,
}).next()
}
/// Get the first setter attribute
fn setter(&self) -> Option<Option<Ident>> {
self.attrs
.iter()
.filter_map(|a| match a {
BindgenAttr::Setter(s) => Some(s.clone()),
_ => None,
}).next()
}
2018-08-07 00:48:03 +03:00
/// Whether the indexing getter attributes is present
2018-08-07 00:06:04 +03:00
fn indexing_getter(&self) -> bool {
self.attrs.iter().any(|a| match *a {
2018-08-07 00:06:04 +03:00
BindgenAttr::IndexingGetter => true,
_ => false,
})
}
2018-08-07 00:48:03 +03:00
/// Whether the indexing setter attributes is present
2018-08-07 00:06:04 +03:00
fn indexing_setter(&self) -> bool {
self.attrs.iter().any(|a| match *a {
2018-08-07 00:06:04 +03:00
BindgenAttr::IndexingSetter => true,
_ => false,
})
}
2018-08-07 00:48:03 +03:00
/// Whether the indexing deleter attributes is present
2018-08-07 00:06:04 +03:00
fn indexing_deleter(&self) -> bool {
self.attrs.iter().any(|a| match *a {
2018-08-07 00:06:04 +03:00
BindgenAttr::IndexingDeleter => true,
_ => false,
})
}
/// Whether the structural attributes is present
fn structural(&self) -> bool {
self.attrs.iter().any(|a| match *a {
BindgenAttr::Structural => true,
_ => false,
})
}
/// Whether the readonly attributes is present
fn readonly(&self) -> bool {
self.attrs.iter().any(|a| match *a {
BindgenAttr::Readonly => true,
_ => false,
})
}
/// Get the first js_name attribute
fn js_name(&self) -> Option<&str> {
self.attrs
.iter()
.filter_map(|a| match a {
BindgenAttr::JsName(s) => Some(&s[..]),
_ => None,
}).next()
}
/// Get the first js_class attribute
fn js_class(&self) -> Option<&str> {
self.attrs
.iter()
.filter_map(|a| match a {
BindgenAttr::JsClass(s) => Some(&s[..]),
_ => None,
}).next()
}
/// Return the list of classes that a type extends
fn extends(&self) -> impl Iterator<Item = &Ident> {
self.attrs.iter().filter_map(|a| match a {
BindgenAttr::Extends(s) => Some(s),
_ => None,
})
}
/// Whether the variadic attributes is present
fn variadic(&self) -> bool {
self.attrs.iter().any(|a| match *a {
BindgenAttr::Variadic => true,
_ => false,
})
}
}
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() } }
));
}
/// The possible attributes in the `#[wasm_bindgen]`.
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub enum BindgenAttr {
Catch,
Constructor,
Method,
StaticMethodOf(Ident),
JsNamespace(Ident),
Module(String),
Getter(Option<Ident>),
Setter(Option<Ident>),
2018-08-07 00:06:04 +03:00
IndexingGetter,
IndexingSetter,
IndexingDeleter,
Structural,
Readonly,
JsName(String),
JsClass(String),
Extends(Ident),
Variadic,
}
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 }
|
2018-08-07 00:06:04 +03:00
call!(term, "indexing_getter") => { |_| BindgenAttr::IndexingGetter }
|
2018-08-07 00:06:04 +03:00
call!(term, "indexing_setter") => { |_| BindgenAttr::IndexingSetter }
|
2018-08-07 00:06:04 +03:00
call!(term, "indexing_deleter") => { |_| BindgenAttr::IndexingDeleter }
|
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, "js_name") >>
punct!(=) >>
name: alt!(
syn!(syn::LitStr) => { |s| s.value() }
|
call!(term2ident) => { |s| s.to_string() }
) >>
(name)
)=> { BindgenAttr::JsName }
|
do_parse!(
call!(term, "js_class") >>
punct!(=) >>
s: syn!(syn::LitStr) >>
(s.value())
)=> { BindgenAttr::JsClass }
|
do_parse!(
call!(term, "extends") >>
punct!(=) >>
ns: call!(term2ident) >>
(ns)
)=> { BindgenAttr::Extends }
|
call!(term, "variadic") => { |_| BindgenAttr::Variadic }
));
}
/// 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 {
return Ok(((), next));
}
}
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),
None => syn::parse_error(),
}
}
/// 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) -> Result<Self::Target, Diagnostic>;
}
impl<'a> ConvertToAst<()> for &'a mut syn::ItemStruct {
type Target = ast::Struct;
fn convert(self, (): ()) -> Result<Self::Target, Diagnostic> {
if self.generics.params.len() > 0 {
bail_span!(
self.generics,
"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)?;
2018-08-21 12:55:09 +01:00
assert_not_variadic(&opts, &field)?;
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<String> = extract_doc_comments(&self.attrs);
Ok(ast::Struct {
name: self.ident.clone(),
fields,
comments,
})
}
}
impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn {
type Target = ast::ImportKind;
fn convert(
self,
(opts, module): (BindgenAttrs, &'a Option<String>),
) -> Result<Self::Target, Diagnostic> {
let default_name = self.ident.to_string();
let js_name = opts.js_name().unwrap_or(&default_name);
let wasm = function_from_decl(
js_name,
self.decl.clone(),
self.attrs.clone(),
self.vis.clone(),
false,
None,
)?.0;
let catch = opts.catch();
let variadic = opts.variadic();
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())?
} 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);
}
2018-08-07 00:06:04 +03:00
if opts.indexing_getter() {
operation_kind = ast::OperationKind::IndexingGetter;
}
2018-08-07 00:06:04 +03:00
if opts.indexing_setter() {
operation_kind = ast::OperationKind::IndexingSetter;
}
2018-08-07 00:06:04 +03:00
if opts.indexing_deleter() {
operation_kind = ast::OperationKind::IndexingDeleter;
}
let kind = if opts.method() {
let class = wasm.arguments.get(0).ok_or_else(|| {
err_span!(self, "imported methods must have at least one argument")
})?;
let class = match class.ty {
syn::Type::Reference(syn::TypeReference {
mutability: None,
ref elem,
..
}) => &**elem,
_ => bail_span!(
class.ty,
"first argument of method must be a shared reference"
),
};
let class_name = match *class {
syn::Type::Path(syn::TypePath {
qself: None,
ref path,
}) => path,
_ => bail_span!(class, "first argument of method must be a path"),
};
let class_name = extract_path_ident(class_name)?;
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 = opts
.js_class()
.map(Into::into)
.unwrap_or_else(|| 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 js_ret {
Some(ref ty) => ty,
_ => bail_span!(self, "constructor returns must be bare types"),
};
let class_name = match *class {
syn::Type::Path(syn::TypePath {
qself: None,
ref path,
}) => path,
_ => bail_span!(self, "return value of constructor must be a bare path"),
};
let class_name = extract_path_ident(class_name)?;
let class_name = opts
.js_class()
.map(Into::into)
.unwrap_or_else(|| class_name.to_string());
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::ScopedMethod { .. } |
ast::ImportFunctionKind::Normal => (0, "n"),
ast::ImportFunctionKind::Method { ref class, .. } => (1, &class[..]),
};
let data = (ns, &self.ident, module);
format!(
"__wbg_{}_{}",
js_name
.chars()
.filter(|c| c.is_ascii_alphanumeric())
.collect::<String>(),
ShortHash(data)
)
};
Ok(ast::ImportKind::Function(ast::ImportFunction {
function: wasm,
kind,
js_ret,
catch,
variadic,
structural: opts.structural(),
rust_name: self.ident.clone(),
shim: Ident::new(&shim, Span::call_site()),
doc_comment: None,
}))
}
}
impl ConvertToAst<BindgenAttrs> for syn::ForeignItemType {
type Target = ast::ImportKind;
fn convert(self, attrs: BindgenAttrs) -> Result<Self::Target, Diagnostic> {
2018-08-21 12:55:09 +01:00
assert_not_variadic(&attrs, &self)?;
let js_name = attrs
.js_name()
.map_or_else(|| self.ident.to_string(), |s| s.to_string());
let shim = format!("__wbg_instanceof_{}_{}", self.ident, ShortHash(&self.ident));
Ok(ast::ImportKind::Type(ast::ImportType {
vis: self.vis,
attrs: self.attrs,
doc_comment: None,
instanceof_shim: shim,
rust_name: self.ident,
js_name,
extends: attrs.extends().cloned().collect(),
}))
}
}
impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemStatic {
type Target = ast::ImportKind;
fn convert(self, (opts, module): (BindgenAttrs, &'a Option<String>))
-> Result<Self::Target, Diagnostic>
{
if self.mutability.is_some() {
bail_span!(self.mutability, "cannot import mutable globals yet")
}
2018-08-21 12:55:09 +01:00
assert_not_variadic(&opts, &self)?;
let default_name = self.ident.to_string();
let js_name = opts.js_name().unwrap_or(&default_name);
let shim = format!(
"__wbg_static_accessor_{}_{}",
self.ident,
ShortHash((&js_name, module, &self.ident)),
);
Ok(ast::ImportKind::Static(ast::ImportStatic {
ty: *self.ty,
vis: self.vis,
rust_name: self.ident.clone(),
js_name: js_name.to_string(),
shim: Ident::new(&shim, Span::call_site()),
}))
}
}
impl ConvertToAst<BindgenAttrs> for syn::ItemFn {
type Target = ast::Function;
fn convert(self, attrs: BindgenAttrs) -> Result<Self::Target, Diagnostic> {
match self.vis {
syn::Visibility::Public(_) => {}
_ => bail_span!(self, "can only #[wasm_bindgen] public functions"),
}
if self.constness.is_some() {
bail_span!(
self.constness,
"can only #[wasm_bindgen] non-const functions"
);
}
if self.unsafety.is_some() {
bail_span!(self.unsafety, "can only #[wasm_bindgen] safe functions");
}
2018-08-21 12:55:09 +01:00
assert_not_variadic(&attrs, &self)?;
let default_name = self.ident.to_string();
let name = attrs.js_name().unwrap_or(&default_name);
Ok(function_from_decl(name, self.decl, self.attrs, self.vis, false, None)?.0)
}
}
/// Construct a function (and gets the self type if appropriate) for our AST from a syn function.
fn function_from_decl(
name: &str,
decl: Box<syn::FnDecl>,
attrs: Vec<syn::Attribute>,
vis: syn::Visibility,
allow_self: bool,
self_ty: Option<&Ident>,
) -> Result<(ast::Function, Option<ast::MethodSelf>), Diagnostic> {
if decl.variadic.is_some() {
bail_span!(decl.variadic, "can't #[wasm_bindgen] variadic functions");
}
if decl.generics.params.len() > 0 {
bail_span!(
decl.generics,
"can't #[wasm_bindgen] functions with lifetime or type parameters",
);
}
assert_no_lifetimes(&decl)?;
let syn::FnDecl { inputs, output, .. } = { *decl };
let replace_self = |t: syn::Type| {
let self_ty = match self_ty {
Some(i) => i,
None => return t,
};
let path = match t {
syn::Type::Path(syn::TypePath { qself: None, path }) => path,
other => return other,
};
let new_path = if path.segments.len() == 1 && path.segments[0].ident == "Self" {
self_ty.clone().into()
} else {
path
};
syn::Type::Path(syn::TypePath {
qself: None,
path: new_path,
})
};
let mut method_self = None;
let arguments = inputs
.into_iter()
.filter_map(|arg| match arg {
syn::FnArg::Captured(mut c) => {
c.ty = replace_self(c.ty);
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::<Vec<_>>();
let ret = match output {
syn::ReturnType::Default => None,
syn::ReturnType::Type(_, ty) => Some(replace_self(*ty)),
};
Ok((
ast::Function {
name: name.to_string(),
arguments,
ret,
rust_vis: vis,
rust_attrs: attrs,
},
method_self,
))
}
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) -> Result<(), Diagnostic>;
}
impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
fn macro_parse(
self,
program: &mut ast::Program,
(opts, tokens): (Option<BindgenAttrs>, &'a mut TokenStream),
Prepare to have targeted error diagnostics (#604) This commit starts to add infrastructure for targeted diagnostics in the `#[wasm_bindgen]` attribute, intended eventually at providing much better errors as they'll be pointing to exactly the code in question rather than always to a `#[wasm_bindgen]` attribute. The general changes are are: * A new `Diagnostic` error type is added to the backend. A `Diagnostic` is created with a textual error or with a span, and it can also be created from a list of diagnostics. A `Diagnostic` implements `ToTokens` which emits a bunch of invocations of `compile_error!` that will cause rustc to later generate errors. * Fallible implementations of `ToTokens` have switched to using a new trait, `TryToTokens`, which returns a `Result` to use `?` with. * The `MacroParse` trait has changed to returning a `Result` to propagate errors upwards. * A new `ui-tests` crate was added which uses `compiletest_rs` to add UI tests. These UI tests will verify that our output improves over time and does not regress. This test suite is added to CI as a new builder as well. * No `Diagnostic` instances are created just yet, everything continues to panic and return `Ok`, with the one exception of the top-level invocations of `syn::parse` which now create a `Diagnostic` and pass it along. This commit does not immediately improve diagnostics but the intention is that it is laying the groundwork for improving diagnostics over time. It should ideally be much easier to contribute improved diagnostics after this commit! cc #601
2018-08-01 17:15:27 -05:00
) -> Result<(), Diagnostic> {
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,
comments,
rust_name: f.ident.clone(),
function: f.convert(opts.unwrap_or_default())?,
});
}
syn::Item::Struct(mut s) => {
program.structs.push((&mut s).convert(())?);
s.to_tokens(tokens);
}
syn::Item::Impl(mut i) => {
Prepare to have targeted error diagnostics (#604) This commit starts to add infrastructure for targeted diagnostics in the `#[wasm_bindgen]` attribute, intended eventually at providing much better errors as they'll be pointing to exactly the code in question rather than always to a `#[wasm_bindgen]` attribute. The general changes are are: * A new `Diagnostic` error type is added to the backend. A `Diagnostic` is created with a textual error or with a span, and it can also be created from a list of diagnostics. A `Diagnostic` implements `ToTokens` which emits a bunch of invocations of `compile_error!` that will cause rustc to later generate errors. * Fallible implementations of `ToTokens` have switched to using a new trait, `TryToTokens`, which returns a `Result` to use `?` with. * The `MacroParse` trait has changed to returning a `Result` to propagate errors upwards. * A new `ui-tests` crate was added which uses `compiletest_rs` to add UI tests. These UI tests will verify that our output improves over time and does not regress. This test suite is added to CI as a new builder as well. * No `Diagnostic` instances are created just yet, everything continues to panic and return `Ok`, with the one exception of the top-level invocations of `syn::parse` which now create a `Diagnostic` and pass it along. This commit does not immediately improve diagnostics but the intention is that it is laying the groundwork for improving diagnostics over time. It should ideally be much easier to contribute improved diagnostics after this commit! cc #601
2018-08-01 17:15:27 -05:00
(&mut i).macro_parse(program, ())?;
i.to_tokens(tokens);
}
syn::Item::ForeignMod(mut f) => {
let opts = match opts {
Some(opts) => opts,
None => BindgenAttrs::find(&mut f.attrs)?,
};
Prepare to have targeted error diagnostics (#604) This commit starts to add infrastructure for targeted diagnostics in the `#[wasm_bindgen]` attribute, intended eventually at providing much better errors as they'll be pointing to exactly the code in question rather than always to a `#[wasm_bindgen]` attribute. The general changes are are: * A new `Diagnostic` error type is added to the backend. A `Diagnostic` is created with a textual error or with a span, and it can also be created from a list of diagnostics. A `Diagnostic` implements `ToTokens` which emits a bunch of invocations of `compile_error!` that will cause rustc to later generate errors. * Fallible implementations of `ToTokens` have switched to using a new trait, `TryToTokens`, which returns a `Result` to use `?` with. * The `MacroParse` trait has changed to returning a `Result` to propagate errors upwards. * A new `ui-tests` crate was added which uses `compiletest_rs` to add UI tests. These UI tests will verify that our output improves over time and does not regress. This test suite is added to CI as a new builder as well. * No `Diagnostic` instances are created just yet, everything continues to panic and return `Ok`, with the one exception of the top-level invocations of `syn::parse` which now create a `Diagnostic` and pass it along. This commit does not immediately improve diagnostics but the intention is that it is laying the groundwork for improving diagnostics over time. It should ideally be much easier to contribute improved diagnostics after this commit! cc #601
2018-08-01 17:15:27 -05:00
f.macro_parse(program, opts)?;
}
syn::Item::Enum(e) => {
e.to_tokens(tokens);
Prepare to have targeted error diagnostics (#604) This commit starts to add infrastructure for targeted diagnostics in the `#[wasm_bindgen]` attribute, intended eventually at providing much better errors as they'll be pointing to exactly the code in question rather than always to a `#[wasm_bindgen]` attribute. The general changes are are: * A new `Diagnostic` error type is added to the backend. A `Diagnostic` is created with a textual error or with a span, and it can also be created from a list of diagnostics. A `Diagnostic` implements `ToTokens` which emits a bunch of invocations of `compile_error!` that will cause rustc to later generate errors. * Fallible implementations of `ToTokens` have switched to using a new trait, `TryToTokens`, which returns a `Result` to use `?` with. * The `MacroParse` trait has changed to returning a `Result` to propagate errors upwards. * A new `ui-tests` crate was added which uses `compiletest_rs` to add UI tests. These UI tests will verify that our output improves over time and does not regress. This test suite is added to CI as a new builder as well. * No `Diagnostic` instances are created just yet, everything continues to panic and return `Ok`, with the one exception of the top-level invocations of `syn::parse` which now create a `Diagnostic` and pass it along. This commit does not immediately improve diagnostics but the intention is that it is laying the groundwork for improving diagnostics over time. It should ideally be much easier to contribute improved diagnostics after this commit! cc #601
2018-08-01 17:15:27 -05:00
e.macro_parse(program, ())?;
}
_ => bail_span!(
self,
"#[wasm_bindgen] can only be applied to a function, \
struct, enum, impl, or extern block"
),
}
Prepare to have targeted error diagnostics (#604) This commit starts to add infrastructure for targeted diagnostics in the `#[wasm_bindgen]` attribute, intended eventually at providing much better errors as they'll be pointing to exactly the code in question rather than always to a `#[wasm_bindgen]` attribute. The general changes are are: * A new `Diagnostic` error type is added to the backend. A `Diagnostic` is created with a textual error or with a span, and it can also be created from a list of diagnostics. A `Diagnostic` implements `ToTokens` which emits a bunch of invocations of `compile_error!` that will cause rustc to later generate errors. * Fallible implementations of `ToTokens` have switched to using a new trait, `TryToTokens`, which returns a `Result` to use `?` with. * The `MacroParse` trait has changed to returning a `Result` to propagate errors upwards. * A new `ui-tests` crate was added which uses `compiletest_rs` to add UI tests. These UI tests will verify that our output improves over time and does not regress. This test suite is added to CI as a new builder as well. * No `Diagnostic` instances are created just yet, everything continues to panic and return `Ok`, with the one exception of the top-level invocations of `syn::parse` which now create a `Diagnostic` and pass it along. This commit does not immediately improve diagnostics but the intention is that it is laying the groundwork for improving diagnostics over time. It should ideally be much easier to contribute improved diagnostics after this commit! cc #601
2018-08-01 17:15:27 -05:00
Ok(())
}
}
impl<'a> MacroParse<()> for &'a mut syn::ItemImpl {
fn macro_parse(self, program: &mut ast::Program, (): ()) -> Result<(), Diagnostic> {
if self.defaultness.is_some() {
bail_span!(
self.defaultness,
"#[wasm_bindgen] default impls are not supported"
);
}
if self.unsafety.is_some() {
bail_span!(
self.unsafety,
"#[wasm_bindgen] unsafe impls are not supported"
);
}
if let Some((_, path, _)) = &self.trait_ {
bail_span!(path, "#[wasm_bindgen] trait impls are not supported");
}
if self.generics.params.len() > 0 {
bail_span!(
self.generics,
"#[wasm_bindgen] generic impls aren't supported"
);
}
let name = match *self.self_ty {
syn::Type::Path(syn::TypePath {
qself: None,
ref path,
}) => extract_path_ident(path)?,
_ => bail_span!(
self.self_ty,
"unsupported self type in #[wasm_bindgen] impl"
),
};
Prepare to have targeted error diagnostics (#604) This commit starts to add infrastructure for targeted diagnostics in the `#[wasm_bindgen]` attribute, intended eventually at providing much better errors as they'll be pointing to exactly the code in question rather than always to a `#[wasm_bindgen]` attribute. The general changes are are: * A new `Diagnostic` error type is added to the backend. A `Diagnostic` is created with a textual error or with a span, and it can also be created from a list of diagnostics. A `Diagnostic` implements `ToTokens` which emits a bunch of invocations of `compile_error!` that will cause rustc to later generate errors. * Fallible implementations of `ToTokens` have switched to using a new trait, `TryToTokens`, which returns a `Result` to use `?` with. * The `MacroParse` trait has changed to returning a `Result` to propagate errors upwards. * A new `ui-tests` crate was added which uses `compiletest_rs` to add UI tests. These UI tests will verify that our output improves over time and does not regress. This test suite is added to CI as a new builder as well. * No `Diagnostic` instances are created just yet, everything continues to panic and return `Ok`, with the one exception of the top-level invocations of `syn::parse` which now create a `Diagnostic` and pass it along. This commit does not immediately improve diagnostics but the intention is that it is laying the groundwork for improving diagnostics over time. It should ideally be much easier to contribute improved diagnostics after this commit! cc #601
2018-08-01 17:15:27 -05:00
let mut errors = Vec::new();
for item in self.items.iter_mut() {
Prepare to have targeted error diagnostics (#604) This commit starts to add infrastructure for targeted diagnostics in the `#[wasm_bindgen]` attribute, intended eventually at providing much better errors as they'll be pointing to exactly the code in question rather than always to a `#[wasm_bindgen]` attribute. The general changes are are: * A new `Diagnostic` error type is added to the backend. A `Diagnostic` is created with a textual error or with a span, and it can also be created from a list of diagnostics. A `Diagnostic` implements `ToTokens` which emits a bunch of invocations of `compile_error!` that will cause rustc to later generate errors. * Fallible implementations of `ToTokens` have switched to using a new trait, `TryToTokens`, which returns a `Result` to use `?` with. * The `MacroParse` trait has changed to returning a `Result` to propagate errors upwards. * A new `ui-tests` crate was added which uses `compiletest_rs` to add UI tests. These UI tests will verify that our output improves over time and does not regress. This test suite is added to CI as a new builder as well. * No `Diagnostic` instances are created just yet, everything continues to panic and return `Ok`, with the one exception of the top-level invocations of `syn::parse` which now create a `Diagnostic` and pass it along. This commit does not immediately improve diagnostics but the intention is that it is laying the groundwork for improving diagnostics over time. It should ideally be much easier to contribute improved diagnostics after this commit! cc #601
2018-08-01 17:15:27 -05:00
if let Err(e) = (&name, item).macro_parse(program, ()) {
errors.push(e);
}
}
Prepare to have targeted error diagnostics (#604) This commit starts to add infrastructure for targeted diagnostics in the `#[wasm_bindgen]` attribute, intended eventually at providing much better errors as they'll be pointing to exactly the code in question rather than always to a `#[wasm_bindgen]` attribute. The general changes are are: * A new `Diagnostic` error type is added to the backend. A `Diagnostic` is created with a textual error or with a span, and it can also be created from a list of diagnostics. A `Diagnostic` implements `ToTokens` which emits a bunch of invocations of `compile_error!` that will cause rustc to later generate errors. * Fallible implementations of `ToTokens` have switched to using a new trait, `TryToTokens`, which returns a `Result` to use `?` with. * The `MacroParse` trait has changed to returning a `Result` to propagate errors upwards. * A new `ui-tests` crate was added which uses `compiletest_rs` to add UI tests. These UI tests will verify that our output improves over time and does not regress. This test suite is added to CI as a new builder as well. * No `Diagnostic` instances are created just yet, everything continues to panic and return `Ok`, with the one exception of the top-level invocations of `syn::parse` which now create a `Diagnostic` and pass it along. This commit does not immediately improve diagnostics but the intention is that it is laying the groundwork for improving diagnostics over time. It should ideally be much easier to contribute improved diagnostics after this commit! cc #601
2018-08-01 17:15:27 -05:00
Diagnostic::from_vec(errors)
}
}
impl<'a, 'b> MacroParse<()> for (&'a Ident, &'b mut syn::ImplItem) {
fn macro_parse(self, program: &mut ast::Program, (): ()) -> Result<(), Diagnostic> {
let (class, item) = self;
let method = match item {
syn::ImplItem::Method(ref mut m) => m,
syn::ImplItem::Const(_) => {
bail_span!(
&*item,
"const definitions aren't supported with #[wasm_bindgen]"
);
}
syn::ImplItem::Type(_) => bail_span!(
&*item,
"type definitions in impls aren't supported with #[wasm_bindgen]"
),
syn::ImplItem::Macro(_) => {
bail_span!(&*item, "macros in impls aren't supported");
}
syn::ImplItem::Verbatim(_) => panic!("unparsed impl item?"),
};
match method.vis {
syn::Visibility::Public(_) => {}
Prepare to have targeted error diagnostics (#604) This commit starts to add infrastructure for targeted diagnostics in the `#[wasm_bindgen]` attribute, intended eventually at providing much better errors as they'll be pointing to exactly the code in question rather than always to a `#[wasm_bindgen]` attribute. The general changes are are: * A new `Diagnostic` error type is added to the backend. A `Diagnostic` is created with a textual error or with a span, and it can also be created from a list of diagnostics. A `Diagnostic` implements `ToTokens` which emits a bunch of invocations of `compile_error!` that will cause rustc to later generate errors. * Fallible implementations of `ToTokens` have switched to using a new trait, `TryToTokens`, which returns a `Result` to use `?` with. * The `MacroParse` trait has changed to returning a `Result` to propagate errors upwards. * A new `ui-tests` crate was added which uses `compiletest_rs` to add UI tests. These UI tests will verify that our output improves over time and does not regress. This test suite is added to CI as a new builder as well. * No `Diagnostic` instances are created just yet, everything continues to panic and return `Ok`, with the one exception of the top-level invocations of `syn::parse` which now create a `Diagnostic` and pass it along. This commit does not immediately improve diagnostics but the intention is that it is laying the groundwork for improving diagnostics over time. It should ideally be much easier to contribute improved diagnostics after this commit! cc #601
2018-08-01 17:15:27 -05:00
_ => return Ok(()),
}
if method.defaultness.is_some() {
panic!("default methods are not supported");
}
if method.sig.constness.is_some() {
bail_span!(
method.sig.constness,
"can only #[wasm_bindgen] non-const functions",
);
}
if method.sig.unsafety.is_some() {
bail_span!(method.sig.unsafety, "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(
opts.js_name().unwrap_or(&method.sig.ident.to_string()),
Box::new(method.sig.decl.clone()),
method.attrs.clone(),
method.vis.clone(),
true,
Some(class),
)?;
program.exports.push(ast::Export {
class: Some(class.clone()),
method_self,
constructor,
function,
comments,
rust_name: method.sig.ident.clone(),
});
Prepare to have targeted error diagnostics (#604) This commit starts to add infrastructure for targeted diagnostics in the `#[wasm_bindgen]` attribute, intended eventually at providing much better errors as they'll be pointing to exactly the code in question rather than always to a `#[wasm_bindgen]` attribute. The general changes are are: * A new `Diagnostic` error type is added to the backend. A `Diagnostic` is created with a textual error or with a span, and it can also be created from a list of diagnostics. A `Diagnostic` implements `ToTokens` which emits a bunch of invocations of `compile_error!` that will cause rustc to later generate errors. * Fallible implementations of `ToTokens` have switched to using a new trait, `TryToTokens`, which returns a `Result` to use `?` with. * The `MacroParse` trait has changed to returning a `Result` to propagate errors upwards. * A new `ui-tests` crate was added which uses `compiletest_rs` to add UI tests. These UI tests will verify that our output improves over time and does not regress. This test suite is added to CI as a new builder as well. * No `Diagnostic` instances are created just yet, everything continues to panic and return `Ok`, with the one exception of the top-level invocations of `syn::parse` which now create a `Diagnostic` and pass it along. This commit does not immediately improve diagnostics but the intention is that it is laying the groundwork for improving diagnostics over time. It should ideally be much easier to contribute improved diagnostics after this commit! cc #601
2018-08-01 17:15:27 -05:00
Ok(())
}
}
impl MacroParse<()> for syn::ItemEnum {
fn macro_parse(self, program: &mut ast::Program, (): ()) -> Result<(), Diagnostic> {
match self.vis {
syn::Visibility::Public(_) => {}
_ => bail_span!(self, "only public enums are allowed with #[wasm_bindgen]"),
}
let variants = self
.variants
.iter()
.enumerate()
.map(|(i, v)| {
match v.fields {
syn::Fields::Unit => (),
_ => bail_span!(v.fields, "only C-Style enums allowed with #[wasm_bindgen]"),
}
let value = match v.discriminant {
Some((
_,
syn::Expr::Lit(syn::ExprLit {
attrs: _,
lit: syn::Lit::Int(ref int_lit),
}),
)) => {
if int_lit.value() > <u32>::max_value() as u64 {
bail_span!(
int_lit,
"enums with #[wasm_bindgen] can only support \
numbers that can be represented as u32"
);
}
int_lit.value() as u32
}
None => i as u32,
Some((_, ref expr)) => bail_span!(
expr,
"enums with #[wasm_bidngen] may only have \
number literal values",
),
};
Ok(ast::Variant {
name: v.ident.clone(),
value,
})
}).collect::<Result<_, Diagnostic>>()?;
let comments = extract_doc_comments(&self.attrs);
program.enums.push(ast::Enum {
name: self.ident,
variants,
comments,
});
Prepare to have targeted error diagnostics (#604) This commit starts to add infrastructure for targeted diagnostics in the `#[wasm_bindgen]` attribute, intended eventually at providing much better errors as they'll be pointing to exactly the code in question rather than always to a `#[wasm_bindgen]` attribute. The general changes are are: * A new `Diagnostic` error type is added to the backend. A `Diagnostic` is created with a textual error or with a span, and it can also be created from a list of diagnostics. A `Diagnostic` implements `ToTokens` which emits a bunch of invocations of `compile_error!` that will cause rustc to later generate errors. * Fallible implementations of `ToTokens` have switched to using a new trait, `TryToTokens`, which returns a `Result` to use `?` with. * The `MacroParse` trait has changed to returning a `Result` to propagate errors upwards. * A new `ui-tests` crate was added which uses `compiletest_rs` to add UI tests. These UI tests will verify that our output improves over time and does not regress. This test suite is added to CI as a new builder as well. * No `Diagnostic` instances are created just yet, everything continues to panic and return `Ok`, with the one exception of the top-level invocations of `syn::parse` which now create a `Diagnostic` and pass it along. This commit does not immediately improve diagnostics but the intention is that it is laying the groundwork for improving diagnostics over time. It should ideally be much easier to contribute improved diagnostics after this commit! cc #601
2018-08-01 17:15:27 -05:00
Ok(())
}
}
impl MacroParse<BindgenAttrs> for syn::ItemForeignMod {
fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> {
let mut errors = Vec::new();
match self.abi.name {
Some(ref l) if l.value() == "C" => {}
None => {}
Some(ref other) => {
errors.push(err_span!(
other,
"only foreign mods with the `C` ABI are allowed"
));
}
}
for mut item in self.items.into_iter() {
if let Err(e) = item.macro_parse(program, &opts) {
errors.push(e);
}
}
Diagnostic::from_vec(errors)
}
}
impl<'a> MacroParse<&'a BindgenAttrs> for syn::ForeignItem {
fn macro_parse(
mut self,
program: &mut ast::Program,
opts: &'a BindgenAttrs,
) -> Result<(), Diagnostic> {
let item_opts = {
let attrs = match self {
syn::ForeignItem::Fn(ref mut f) => &mut f.attrs,
syn::ForeignItem::Type(ref mut t) => &mut t.attrs,
syn::ForeignItem::Static(ref mut s) => &mut s.attrs,
_ => panic!("only foreign functions/types allowed for now"),
};
BindgenAttrs::find(attrs)?
};
let module = item_opts.module().or(opts.module()).map(|s| s.to_string());
let js_namespace = item_opts.js_namespace().or(opts.js_namespace()).cloned();
let kind = match self {
syn::ForeignItem::Fn(f) => f.convert((item_opts, &module))?,
syn::ForeignItem::Type(t) => t.convert(item_opts)?,
syn::ForeignItem::Static(s) => s.convert((item_opts, &module))?,
_ => panic!("only foreign functions/types allowed for now"),
};
program.imports.push(ast::Import {
module,
js_namespace,
kind,
});
Prepare to have targeted error diagnostics (#604) This commit starts to add infrastructure for targeted diagnostics in the `#[wasm_bindgen]` attribute, intended eventually at providing much better errors as they'll be pointing to exactly the code in question rather than always to a `#[wasm_bindgen]` attribute. The general changes are are: * A new `Diagnostic` error type is added to the backend. A `Diagnostic` is created with a textual error or with a span, and it can also be created from a list of diagnostics. A `Diagnostic` implements `ToTokens` which emits a bunch of invocations of `compile_error!` that will cause rustc to later generate errors. * Fallible implementations of `ToTokens` have switched to using a new trait, `TryToTokens`, which returns a `Result` to use `?` with. * The `MacroParse` trait has changed to returning a `Result` to propagate errors upwards. * A new `ui-tests` crate was added which uses `compiletest_rs` to add UI tests. These UI tests will verify that our output improves over time and does not regress. This test suite is added to CI as a new builder as well. * No `Diagnostic` instances are created just yet, everything continues to panic and return `Ok`, with the one exception of the top-level invocations of `syn::parse` which now create a `Diagnostic` and pass it along. This commit does not immediately improve diagnostics but the intention is that it is laying the groundwork for improving diagnostics over time. It should ideally be much easier to contribute improved diagnostics after this commit! cc #601
2018-08-01 17:15:27 -05:00
Ok(())
}
}
/// 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>, Diagnostic> {
let t = match ty {
Some(t) => t,
None => return Ok(None),
};
let path = match *t {
syn::Type::Path(syn::TypePath {
qself: None,
ref path,
}) => path,
_ => bail_span!(t, "must be Result<...>"),
};
let seg = path
.segments
.last()
.ok_or_else(|| err_span!(t, "must have at least one segment"))?
.into_value();
let generics = match seg.arguments {
syn::PathArguments::AngleBracketed(ref t) => t,
_ => bail_span!(t, "must be Result<...>"),
};
let generic = generics
.args
.first()
.ok_or_else(|| err_span!(t, "must have at least one generic parameter"))?
.into_value();
let ty = match generic {
syn::GenericArgument::Type(t) => t,
other => bail_span!(other, "must be a type parameter"),
};
match *ty {
syn::Type::Tuple(ref t) if t.elems.len() == 0 => return Ok(None),
_ => {}
}
Ok(Some(ty.clone()))
}
/// Extract the documentation comments from a Vec of attributes
fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec<String> {
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<String>
.fold(vec![], |mut acc, a| {acc.extend(a); acc})
}
/// Check there are no lifetimes on the function.
fn assert_no_lifetimes(decl: &syn::FnDecl) -> Result<(), Diagnostic> {
struct Walk {
diagnostics: Vec<Diagnostic>,
}
impl<'ast> syn::visit::Visit<'ast> for Walk {
fn visit_lifetime(&mut self, i: &'ast syn::Lifetime) {
self.diagnostics.push(err_span!(
&*i,
"it is currently not sound to use lifetimes in function \
signatures"
));
}
}
let mut walk = Walk {
diagnostics: Vec::new(),
};
syn::visit::Visit::visit_fn_decl(&mut walk, decl);
Diagnostic::from_vec(walk.diagnostics)
}
/// This method always fails if the BindgenAttrs contain variadic
2018-08-21 12:55:09 +01:00
fn assert_not_variadic(attrs: &BindgenAttrs, span: &dyn ToTokens) -> Result<(), Diagnostic> {
if attrs.variadic() {
bail_span!(span, "the `variadic` attribute can only be applied to imported \
(`extern`) functions")
}
2018-08-21 12:55:09 +01:00
Ok(())
}
/// If the path is a single ident, return it.
fn extract_path_ident(path: &syn::Path) -> Result<Ident, Diagnostic> {
if path.leading_colon.is_some() {
bail_span!(path, "global paths are not supported yet");
}
if path.segments.len() != 1 {
bail_span!(path, "multi-segment paths are not supported yet");
}
let value = &path.segments[0];
match value.arguments {
syn::PathArguments::None => {}
_ => bail_span!(path, "paths with type parameters are not supported yet"),
}
Ok(value.ident.clone())
}