Result-ify src/parser.rs (#608)

* Make ConvertToAst trait fallible

It's got some panics, and we'll be switching those to errors!

* First example of a diagnostic-driven error

Add a diagnostic-driven error `#[wasm_bindgen]` being attached to public
functions, and add some macros to boot to make it easier to generate errors!

* Result-ify `src/parser.rs`

This commit converts all of `src/parser.rs` away from panics to using
`Diagnostic` instead. Along the way this adds a test case per changed `panic!`,
ensuring that we don't regress in these areas!
This commit is contained in:
Alex Crichton 2018-08-01 18:59:59 -05:00 committed by GitHub
parent d90802a40c
commit bdec2582aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 667 additions and 137 deletions

View File

@ -1,6 +1,20 @@
use proc_macro2::*; use proc_macro2::*;
use quote::ToTokens; use quote::ToTokens;
#[macro_export]
macro_rules! err_span {
($span:expr, $($msg:tt)*) => (
::backend::Diagnostic::span_error(&$span, format!($($msg)*))
)
}
#[macro_export]
macro_rules! bail_span {
($($t:tt)*) => (
return Err(err_span!($($t)*).into())
)
}
pub struct Diagnostic { pub struct Diagnostic {
inner: Repr, inner: Repr,
} }

1
crates/macro-support/src/lib.rs Executable file → Normal file
View File

@ -7,6 +7,7 @@ extern crate proc_macro2;
extern crate quote; extern crate quote;
#[macro_use] #[macro_use]
extern crate syn; extern crate syn;
#[macro_use]
extern crate wasm_bindgen_backend as backend; extern crate wasm_bindgen_backend as backend;
extern crate wasm_bindgen_shared as shared; extern crate wasm_bindgen_shared as shared;

View File

@ -1,7 +1,7 @@
use backend::ast; use backend::ast;
use backend::Diagnostic; use backend::Diagnostic;
use backend::util::{ident_ty, ShortHash}; use backend::util::{ident_ty, ShortHash};
use proc_macro2::{Ident, Span, TokenStream, TokenTree}; use proc_macro2::{Ident, Span, TokenStream, TokenTree, Delimiter};
use quote::ToTokens; use quote::ToTokens;
use shared; use shared;
use syn; use syn;
@ -16,7 +16,7 @@ pub struct BindgenAttrs {
impl BindgenAttrs { impl BindgenAttrs {
/// Find and parse the wasm_bindgen attributes. /// Find and parse the wasm_bindgen attributes.
fn find(attrs: &mut Vec<syn::Attribute>) -> BindgenAttrs { fn find(attrs: &mut Vec<syn::Attribute>) -> Result<BindgenAttrs, Diagnostic> {
let pos = attrs let pos = attrs
.iter() .iter()
.enumerate() .enumerate()
@ -24,18 +24,22 @@ impl BindgenAttrs {
.map(|a| a.0); .map(|a| a.0);
let pos = match pos { let pos = match pos {
Some(i) => i, Some(i) => i,
None => return BindgenAttrs::default(), None => return Ok(BindgenAttrs::default()),
}; };
let mut tts = attrs.remove(pos).tts.into_iter(); let attr = attrs.remove(pos);
let tt = match tts.next() { let mut tts = attr.tts.clone().into_iter();
Some(TokenTree::Group(d)) => d.stream(), let group = match tts.next() {
Some(_) => panic!("malformed #[wasm_bindgen] attribute"), Some(TokenTree::Group(d)) => d,
None => return BindgenAttrs::default(), Some(_) => bail_span!(attr, "malformed #[wasm_bindgen] attribute"),
None => return Ok(BindgenAttrs::default()),
}; };
if tts.next().is_some() { if tts.next().is_some() {
panic!("malformed #[wasm_bindgen] attribute"); bail_span!(attr, "malformed #[wasm_bindgen] attribute");
} }
syn::parse(tt.into()).expect("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 /// Get the first module attribute
@ -304,15 +308,16 @@ trait ConvertToAst<Ctx> {
/// Convert into our target. /// Convert into our target.
/// ///
/// Since this is used in a procedural macro, use panic to fail. /// Since this is used in a procedural macro, use panic to fail.
fn convert(self, context: Ctx) -> Self::Target; fn convert(self, context: Ctx) -> Result<Self::Target, Diagnostic>;
} }
impl<'a> ConvertToAst<()> for &'a mut syn::ItemStruct { impl<'a> ConvertToAst<()> for &'a mut syn::ItemStruct {
type Target = ast::Struct; type Target = ast::Struct;
fn convert(self, (): ()) -> Self::Target { fn convert(self, (): ()) -> Result<Self::Target, Diagnostic> {
if self.generics.params.len() > 0 { if self.generics.params.len() > 0 {
panic!( bail_span!(
self.generics,
"structs with #[wasm_bindgen] cannot have lifetime or \ "structs with #[wasm_bindgen] cannot have lifetime or \
type parameters currently" type parameters currently"
); );
@ -332,7 +337,7 @@ impl<'a> ConvertToAst<()> for &'a mut syn::ItemStruct {
let name_str = name.to_string(); let name_str = name.to_string();
let getter = shared::struct_field_get(&ident, &name_str); let getter = shared::struct_field_get(&ident, &name_str);
let setter = shared::struct_field_set(&ident, &name_str); let setter = shared::struct_field_set(&ident, &name_str);
let opts = BindgenAttrs::find(&mut field.attrs); let opts = BindgenAttrs::find(&mut field.attrs)?;
let comments = extract_doc_comments(&field.attrs); let comments = extract_doc_comments(&field.attrs);
fields.push(ast::StructField { fields.push(ast::StructField {
name: name.clone(), name: name.clone(),
@ -346,20 +351,28 @@ impl<'a> ConvertToAst<()> for &'a mut syn::ItemStruct {
} }
} }
let comments: Vec<String> = extract_doc_comments(&self.attrs); let comments: Vec<String> = extract_doc_comments(&self.attrs);
ast::Struct { Ok(ast::Struct {
name: self.ident.clone(), name: self.ident.clone(),
fields, fields,
comments, comments,
} })
} }
} }
impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn { impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn {
type Target = ast::ImportKind; type Target = ast::ImportKind;
fn convert(self, (opts, module): (BindgenAttrs, &'a Option<String>)) -> Self::Target { fn convert(self, (opts, module): (BindgenAttrs, &'a Option<String>))
-> Result<Self::Target, Diagnostic>
{
let js_name = opts.js_name().unwrap_or(&self.ident).clone(); 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 wasm = function_from_decl(
&js_name,
self.decl.clone(),
self.attrs.clone(),
self.vis.clone(),
false,
)?.0;
let catch = opts.catch(); let catch = opts.catch();
let js_ret = if catch { let js_ret = if catch {
// TODO: this assumes a whole bunch: // TODO: this assumes a whole bunch:
@ -369,8 +382,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn
// * The actual type is the first type parameter // * The actual type is the first type parameter
// //
// should probably fix this one day... // should probably fix this one day...
extract_first_ty_param(wasm.ret.as_ref()) extract_first_ty_param(wasm.ret.as_ref())?
.expect("can't `catch` without returning a Result")
} else { } else {
wasm.ret.clone() wasm.ret.clone()
}; };
@ -387,24 +399,27 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn
let class = wasm let class = wasm
.arguments .arguments
.get(0) .get(0)
.expect("methods must have at least one argument"); .ok_or_else(|| {
err_span!(self, "imported methods must have at least one argument")
})?;
let class = match class.ty { let class = match class.ty {
syn::Type::Reference(syn::TypeReference { syn::Type::Reference(syn::TypeReference {
mutability: None, mutability: None,
ref elem, ref elem,
.. ..
}) => &**elem, }) => &**elem,
_ => panic!("first argument of method must be a shared reference"), _ => {
bail_span!(class.ty, "first argument of method must be a shared reference")
}
}; };
let class_name = match *class { let class_name = match *class {
syn::Type::Path(syn::TypePath { syn::Type::Path(syn::TypePath {
qself: None, qself: None,
ref path, ref path,
}) => path, }) => path,
_ => panic!("first argument of method must be a path"), _ => bail_span!(class, "first argument of method must be a path"),
}; };
let class_name = extract_path_ident(class_name) let class_name = extract_path_ident(class_name)?;
.expect("first argument of method must be a bare type");
let class_name = opts let class_name = opts
.js_class() .js_class()
.map(Into::into) .map(Into::into)
@ -433,17 +448,16 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn
} else if opts.constructor() { } else if opts.constructor() {
let class = match wasm.ret { let class = match wasm.ret {
Some(ref ty) => ty, Some(ref ty) => ty,
_ => panic!("constructor returns must be bare types"), _ => bail_span!(self, "constructor returns must be bare types"),
}; };
let class_name = match *class { let class_name = match *class {
syn::Type::Path(syn::TypePath { syn::Type::Path(syn::TypePath {
qself: None, qself: None,
ref path, ref path,
}) => path, }) => path,
_ => panic!("first argument of method must be a path"), _ => bail_span!(self, "return value of constructor must be a bare path"),
}; };
let class_name = extract_path_ident(class_name) let class_name = extract_path_ident(class_name)?;
.expect("first argument of method must be a bare type");
ast::ImportFunctionKind::Method { ast::ImportFunctionKind::Method {
class: class_name.to_string(), class: class_name.to_string(),
@ -462,7 +476,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn
let data = (ns, &self.ident, module); let data = (ns, &self.ident, module);
format!("__wbg_{}_{}", js_name, ShortHash(data)) format!("__wbg_{}_{}", js_name, ShortHash(data))
}; };
ast::ImportKind::Function(ast::ImportFunction { Ok(ast::ImportKind::Function(ast::ImportFunction {
function: wasm, function: wasm,
kind, kind,
js_ret, js_ret,
@ -471,59 +485,59 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn
rust_name: self.ident.clone(), rust_name: self.ident.clone(),
shim: Ident::new(&shim, Span::call_site()), shim: Ident::new(&shim, Span::call_site()),
doc_comment: None, doc_comment: None,
}) }))
} }
} }
impl ConvertToAst<()> for syn::ForeignItemType { impl ConvertToAst<()> for syn::ForeignItemType {
type Target = ast::ImportKind; type Target = ast::ImportKind;
fn convert(self, (): ()) -> Self::Target { fn convert(self, (): ()) -> Result<Self::Target, Diagnostic> {
ast::ImportKind::Type(ast::ImportType { Ok(ast::ImportKind::Type(ast::ImportType {
vis: self.vis, vis: self.vis,
name: self.ident, name: self.ident,
attrs: self.attrs, attrs: self.attrs,
doc_comment: None, doc_comment: None,
}) }))
} }
} }
impl ConvertToAst<BindgenAttrs> for syn::ForeignItemStatic { impl ConvertToAst<BindgenAttrs> for syn::ForeignItemStatic {
type Target = ast::ImportKind; type Target = ast::ImportKind;
fn convert(self, opts: BindgenAttrs) -> Self::Target { fn convert(self, opts: BindgenAttrs) -> Result<Self::Target, Diagnostic> {
if self.mutability.is_some() { if self.mutability.is_some() {
panic!("cannot import mutable globals yet") bail_span!(self.mutability, "cannot import mutable globals yet")
} }
let js_name = opts.js_name().unwrap_or(&self.ident); let js_name = opts.js_name().unwrap_or(&self.ident);
let shim = format!("__wbg_static_accessor_{}_{}", js_name, self.ident); let shim = format!("__wbg_static_accessor_{}_{}", js_name, self.ident);
ast::ImportKind::Static(ast::ImportStatic { Ok(ast::ImportKind::Static(ast::ImportStatic {
ty: *self.ty, ty: *self.ty,
vis: self.vis, vis: self.vis,
rust_name: self.ident.clone(), rust_name: self.ident.clone(),
js_name: js_name.clone(), js_name: js_name.clone(),
shim: Ident::new(&shim, Span::call_site()), shim: Ident::new(&shim, Span::call_site()),
}) }))
} }
} }
impl ConvertToAst<BindgenAttrs> for syn::ItemFn { impl ConvertToAst<BindgenAttrs> for syn::ItemFn {
type Target = ast::Function; type Target = ast::Function;
fn convert(self, attrs: BindgenAttrs) -> Self::Target { fn convert(self, attrs: BindgenAttrs) -> Result<Self::Target, Diagnostic> {
match self.vis { match self.vis {
syn::Visibility::Public(_) => {} syn::Visibility::Public(_) => {}
_ => panic!("can only bindgen public functions"), _ => bail_span!(self, "can only #[wasm_bindgen] public functions"),
} }
if self.constness.is_some() { if self.constness.is_some() {
panic!("can only bindgen non-const functions"); bail_span!(self.constness, "can only #[wasm_bindgen] non-const functions");
} }
if self.unsafety.is_some() { if self.unsafety.is_some() {
panic!("can only bindgen safe functions"); bail_span!(self.unsafety, "can only #[wasm_bindgen] safe functions");
} }
let name = attrs.js_name().unwrap_or(&self.ident); let name = attrs.js_name().unwrap_or(&self.ident);
function_from_decl(name, self.decl, self.attrs, self.vis, false).0 Ok(function_from_decl(name, self.decl, self.attrs, self.vis, false)?.0)
} }
} }
@ -534,15 +548,18 @@ fn function_from_decl(
attrs: Vec<syn::Attribute>, attrs: Vec<syn::Attribute>,
vis: syn::Visibility, vis: syn::Visibility,
allow_self: bool, allow_self: bool,
) -> (ast::Function, Option<ast::MethodSelf>) { ) -> Result<(ast::Function, Option<ast::MethodSelf>), Diagnostic> {
if decl.variadic.is_some() { if decl.variadic.is_some() {
panic!("can't bindgen variadic functions") bail_span!(decl.variadic, "can't #[wasm_bindgen] variadic functions");
} }
if decl.generics.params.len() > 0 { if decl.generics.params.len() > 0 {
panic!("can't bindgen functions with lifetime or type parameters") bail_span!(
decl.generics,
"can't #[wasm_bindgen] functions with lifetime or type parameters",
);
} }
assert_no_lifetimes(&mut decl); assert_no_lifetimes(&mut decl)?;
let syn::FnDecl { inputs, output, .. } = { *decl }; let syn::FnDecl { inputs, output, .. } = { *decl };
@ -574,7 +591,7 @@ fn function_from_decl(
syn::ReturnType::Type(_, ty) => Some(*ty), syn::ReturnType::Type(_, ty) => Some(*ty),
}; };
( Ok((
ast::Function { ast::Function {
name: name.clone(), name: name.clone(),
arguments, arguments,
@ -583,7 +600,7 @@ fn function_from_decl(
rust_attrs: attrs, rust_attrs: attrs,
}, },
method_self, method_self,
) ))
} }
pub(crate) trait MacroParse<Ctx> { pub(crate) trait MacroParse<Ctx> {
@ -623,11 +640,11 @@ impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
constructor: None, constructor: None,
comments, comments,
rust_name: f.ident.clone(), rust_name: f.ident.clone(),
function: f.convert(opts.unwrap_or_default()), function: f.convert(opts.unwrap_or_default())?,
}); });
} }
syn::Item::Struct(mut s) => { syn::Item::Struct(mut s) => {
program.structs.push((&mut s).convert(())); program.structs.push((&mut s).convert(())?);
s.to_tokens(tokens); s.to_tokens(tokens);
} }
syn::Item::Impl(mut i) => { syn::Item::Impl(mut i) => {
@ -635,17 +652,23 @@ impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
i.to_tokens(tokens); i.to_tokens(tokens);
} }
syn::Item::ForeignMod(mut f) => { syn::Item::ForeignMod(mut f) => {
let opts = opts.unwrap_or_else(|| BindgenAttrs::find(&mut f.attrs)); let opts = match opts {
Some(opts) => opts,
None => BindgenAttrs::find(&mut f.attrs)?,
};
f.macro_parse(program, opts)?; f.macro_parse(program, opts)?;
} }
syn::Item::Enum(e) => { syn::Item::Enum(e) => {
e.to_tokens(tokens); e.to_tokens(tokens);
e.macro_parse(program, ())?; e.macro_parse(program, ())?;
} }
_ => panic!( _ => {
"#[wasm_bindgen] can only be applied to a function, \ bail_span!(
struct, enum, impl, or extern block" self,
), "#[wasm_bindgen] can only be applied to a function, \
struct, enum, impl, or extern block"
)
}
} }
Ok(()) Ok(())
@ -657,26 +680,23 @@ impl<'a> MacroParse<()> for &'a mut syn::ItemImpl {
-> Result<(), Diagnostic> -> Result<(), Diagnostic>
{ {
if self.defaultness.is_some() { if self.defaultness.is_some() {
panic!("default impls are not supported"); bail_span!(self.defaultness, "#[wasm_bindgen] default impls are not supported");
} }
if self.unsafety.is_some() { if self.unsafety.is_some() {
panic!("unsafe impls are not supported"); bail_span!(self.unsafety, "#[wasm_bindgen] unsafe impls are not supported");
} }
if self.trait_.is_some() { if let Some((_, path, _)) = &self.trait_ {
panic!("trait impls are not supported"); bail_span!(path, "#[wasm_bindgen] trait impls are not supported");
} }
if self.generics.params.len() > 0 { if self.generics.params.len() > 0 {
panic!("generic impls aren't supported"); bail_span!(self.generics, "#[wasm_bindgen] generic impls aren't supported");
} }
let name = match *self.self_ty { let name = match *self.self_ty {
syn::Type::Path(syn::TypePath { syn::Type::Path(syn::TypePath {
qself: None, qself: None,
ref path, ref path,
}) => match extract_path_ident(path) { }) => extract_path_ident(path)?,
Some(ident) => ident, _ => bail_span!(self.self_ty, "unsupported self type in #[wasm_bindgen] impl"),
None => panic!("unsupported self type in impl"),
},
_ => panic!("unsupported self type in impl"),
}; };
let mut errors = Vec::new(); let mut errors = Vec::new();
for item in self.items.iter_mut() { for item in self.items.iter_mut() {
@ -695,10 +715,16 @@ impl<'a, 'b> MacroParse<()> for (&'a Ident, &'b mut syn::ImplItem) {
let (class, item) = self; let (class, item) = self;
replace_self(class, item); replace_self(class, item);
let method = match 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::Method(ref mut m) => m,
syn::ImplItem::Macro(_) => panic!("macros in impls aren't supported"), 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?"), syn::ImplItem::Verbatim(_) => panic!("unparsed impl item?"),
}; };
match method.vis { match method.vis {
@ -709,13 +735,19 @@ impl<'a, 'b> MacroParse<()> for (&'a Ident, &'b mut syn::ImplItem) {
panic!("default methods are not supported"); panic!("default methods are not supported");
} }
if method.sig.constness.is_some() { if method.sig.constness.is_some() {
panic!("can only bindgen non-const functions"); bail_span!(
method.sig.constness,
"can only #[wasm_bindgen] non-const functions",
);
} }
if method.sig.unsafety.is_some() { if method.sig.unsafety.is_some() {
panic!("can only bindgen safe functions"); bail_span!(
method.sig.unsafety,
"can only bindgen safe functions",
);
} }
let opts = BindgenAttrs::find(&mut method.attrs); let opts = BindgenAttrs::find(&mut method.attrs)?;
let comments = extract_doc_comments(&method.attrs); let comments = extract_doc_comments(&method.attrs);
let is_constructor = opts.constructor(); let is_constructor = opts.constructor();
let constructor = if is_constructor { let constructor = if is_constructor {
@ -730,7 +762,7 @@ impl<'a, 'b> MacroParse<()> for (&'a Ident, &'b mut syn::ImplItem) {
method.attrs.clone(), method.attrs.clone(),
method.vis.clone(), method.vis.clone(),
true, true,
); )?;
program.exports.push(ast::Export { program.exports.push(ast::Export {
class: Some(class.clone()), class: Some(class.clone()),
@ -750,7 +782,7 @@ impl MacroParse<()> for syn::ItemEnum {
{ {
match self.vis { match self.vis {
syn::Visibility::Public(_) => {} syn::Visibility::Public(_) => {}
_ => panic!("only public enums are allowed"), _ => bail_span!(self, "only public enums are allowed with #[wasm_bindgen]"),
} }
let variants = self let variants = self
@ -760,7 +792,7 @@ impl MacroParse<()> for syn::ItemEnum {
.map(|(i, v)| { .map(|(i, v)| {
match v.fields { match v.fields {
syn::Fields::Unit => (), syn::Fields::Unit => (),
_ => panic!("Only C-Style enums allowed"), _ => bail_span!(v.fields, "only C-Style enums allowed with #[wasm_bindgen]"),
} }
let value = match v.discriminant { let value = match v.discriminant {
Some(( Some((
@ -771,20 +803,30 @@ impl MacroParse<()> for syn::ItemEnum {
}), }),
)) => { )) => {
if int_lit.value() > <u32>::max_value() as u64 { if int_lit.value() > <u32>::max_value() as u64 {
panic!("Enums can only support numbers that can be represented as u32"); bail_span!(
int_lit,
"enums with #[wasm_bindgen] can only support \
numbers that can be represented as u32"
);
} }
int_lit.value() as u32 int_lit.value() as u32
} }
None => i as u32, None => i as u32,
_ => panic!("Enums may only have number literal values"), Some((_, ref expr)) => {
bail_span!(
expr,
"enums with #[wasm_bidngen] may only have \
number literal values",
)
}
}; };
ast::Variant { Ok(ast::Variant {
name: v.ident.clone(), name: v.ident.clone(),
value, value,
} })
}) })
.collect(); .collect::<Result<_, Diagnostic>>()?;
let comments = extract_doc_comments(&self.attrs); let comments = extract_doc_comments(&self.attrs);
program.enums.push(ast::Enum { program.enums.push(ast::Enum {
name: self.ident, name: self.ident,
@ -799,47 +841,62 @@ impl MacroParse<BindgenAttrs> for syn::ItemForeignMod {
fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs)
-> Result<(), Diagnostic> -> Result<(), Diagnostic>
{ {
let mut errors = Vec::new();
match self.abi.name { match self.abi.name {
Some(ref l) if l.value() == "C" => {} Some(ref l) if l.value() == "C" => {}
None => {} None => {}
_ => panic!("only foreign mods with the `C` ABI are allowed"), 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() { for mut item in self.items.into_iter() {
let item_opts = { if let Err(e) = item.macro_parse(program, &opts) {
let attrs = match item { errors.push(e);
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, Diagnostic::from_vec(errors)
_ => panic!("only foreign functions/types allowed for now"), }
}; }
BindgenAttrs::find(attrs)
}; impl<'a> MacroParse<&'a BindgenAttrs> for syn::ForeignItem {
let module = item_opts.module().or(opts.module()).map(|s| s.to_string()); fn macro_parse(mut self, program: &mut ast::Program, opts: &'a BindgenAttrs)
let version = item_opts -> Result<(), Diagnostic>
.version() {
.or(opts.version()) let item_opts = {
.map(|s| s.to_string()); let attrs = match self {
let js_namespace = item_opts.js_namespace().or(opts.js_namespace()).cloned(); syn::ForeignItem::Fn(ref mut f) => &mut f.attrs,
let mut kind = match item { syn::ForeignItem::Type(ref mut t) => &mut t.attrs,
syn::ForeignItem::Fn(f) => f.convert((item_opts, &module)), syn::ForeignItem::Static(ref mut s) => &mut s.attrs,
syn::ForeignItem::Type(t) => t.convert(()),
syn::ForeignItem::Static(s) => s.convert(item_opts),
_ => panic!("only foreign functions/types allowed for now"), _ => 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 kind = match self {
syn::ForeignItem::Fn(f) => f.convert((item_opts, &module))?,
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,
});
program.imports.push(ast::Import {
module,
version,
js_namespace,
kind,
});
}
Ok(()) Ok(())
} }
} }
/// Get the first type parameter of a generic type, errors on incorrect input. /// 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>, ()> { fn extract_first_ty_param(ty: Option<&syn::Type>) -> Result<Option<syn::Type>, Diagnostic> {
let t = match ty { let t = match ty {
Some(t) => t, Some(t) => t,
None => return Ok(None), None => return Ok(None),
@ -849,16 +906,21 @@ fn extract_first_ty_param(ty: Option<&syn::Type>) -> Result<Option<syn::Type>, (
qself: None, qself: None,
ref path, ref path,
}) => path, }) => path,
_ => return Err(()), _ => bail_span!(t, "must be Result<...>"),
}; };
let seg = path.segments.last().ok_or(())?.into_value(); let seg = path.segments.last()
.ok_or_else(|| err_span!(t, "must have at least one segment"))?
.into_value();
let generics = match seg.arguments { let generics = match seg.arguments {
syn::PathArguments::AngleBracketed(ref t) => t, syn::PathArguments::AngleBracketed(ref t) => t,
_ => return Err(()), _ => bail_span!(t, "must be Result<...>"),
}; };
let ty = match *generics.args.first().ok_or(())?.into_value() { let generic = generics.args.first()
syn::GenericArgument::Type(ref t) => t, .ok_or_else(|| err_span!(t, "must have at least one generic parameter"))?
_ => return Err(()), .into_value();
let ty = match generic {
syn::GenericArgument::Type(t) => t,
other => bail_span!(other, "must be a type parameter"),
}; };
match *ty { match *ty {
syn::Type::Tuple(ref t) if t.elems.len() == 0 => return Ok(None), syn::Type::Tuple(ref t) if t.elems.len() == 0 => return Ok(None),
@ -910,32 +972,37 @@ fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec<String> {
} }
/// Check there are no lifetimes on the function. /// Check there are no lifetimes on the function.
fn assert_no_lifetimes(decl: &mut syn::FnDecl) { fn assert_no_lifetimes(decl: &mut syn::FnDecl) -> Result<(), Diagnostic> {
struct Walk; struct Walk {
diagnostics: Vec<Diagnostic>,
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); impl<'ast> syn::visit_mut::VisitMut for Walk {
fn visit_lifetime_mut(&mut self, i: &mut 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_mut::VisitMut::visit_fn_decl_mut(&mut walk, decl);
Diagnostic::from_vec(walk.diagnostics)
} }
/// If the path is a single ident, return it. /// If the path is a single ident, return it.
fn extract_path_ident(path: &syn::Path) -> Option<Ident> { fn extract_path_ident(path: &syn::Path) -> Result<Ident, Diagnostic> {
if path.leading_colon.is_some() { if path.leading_colon.is_some() {
return None; bail_span!(path, "global paths are not supported yet");
} }
if path.segments.len() != 1 { if path.segments.len() != 1 {
return None; bail_span!(path, "multi-segment paths are not supported yet");
} }
match path.segments.first().unwrap().value().arguments { let value = &path.segments[0];
match value.arguments {
syn::PathArguments::None => {} syn::PathArguments::None => {}
_ => return None, _ => bail_span!(path, "paths with type parameters are not supported yet"),
} }
path.segments.first().map(|v| v.value().ident.clone()) Ok(value.ident.clone())
} }

View File

@ -0,0 +1,23 @@
#![feature(use_extern_macros)]
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen(x)]
pub fn foo() {}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(y)]
fn bar();
#[wasm_bindgen z]
fn bar();
#[wasm_bindgen(z2) x]
fn bar();
#[wasm_bindgen { }]
fn bar();
}

View File

@ -0,0 +1,32 @@
error: error parsing #[wasm_bindgen] attribute options: failed to parse anything
--> $DIR/invalid-attr.rs:7:16
|
7 | #[wasm_bindgen(x)]
| ^
error: error parsing #[wasm_bindgen] attribute options: failed to parse anything
--> $DIR/invalid-attr.rs:12:20
|
12 | #[wasm_bindgen(y)]
| ^
error: malformed #[wasm_bindgen] attribute
--> $DIR/invalid-attr.rs:15:5
|
15 | #[wasm_bindgen z]
| ^^^^^^^^^^^^^^^^^
error: malformed #[wasm_bindgen] attribute
--> $DIR/invalid-attr.rs:18:5
|
18 | #[wasm_bindgen(z2) x]
| ^^^^^^^^^^^^^^^^^^^^^
error: malformed #[wasm_bindgen] attribute
--> $DIR/invalid-attr.rs:21:5
|
21 | #[wasm_bindgen { }]
| ^^^^^^^^^^^^^^^^^^^^
error: aborting due to 5 previous errors

View File

@ -0,0 +1,23 @@
#![feature(use_extern_macros)]
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
enum A {}
#[wasm_bindgen]
pub enum B {
D(u32),
}
#[wasm_bindgen]
pub enum C {
X = 1 + 3,
}
#[wasm_bindgen]
pub enum D {
X = 4294967296,
}

View File

@ -0,0 +1,26 @@
error: only public enums are allowed with #[wasm_bindgen]
--> $DIR/invalid-enums.rs:8:1
|
8 | enum A {}
| ^^^^^^^^^
error: only C-Style enums allowed with #[wasm_bindgen]
--> $DIR/invalid-enums.rs:12:6
|
12 | D(u32),
| ^^^^^
error: enums with #[wasm_bidngen] may only have number literal values
--> $DIR/invalid-enums.rs:17:9
|
17 | X = 1 + 3,
| ^^^^^
error: enums with #[wasm_bindgen] can only support numbers that can be represented as u32
--> $DIR/invalid-enums.rs:22:9
|
22 | X = 4294967296,
| ^^^^^^^^^^
error: aborting due to 4 previous errors

View File

@ -0,0 +1,43 @@
#![feature(use_extern_macros)]
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
type A;
fn f() -> &'static u32;
#[wasm_bindgen(method)]
fn f1();
#[wasm_bindgen(method)]
fn f2(x: u32);
#[wasm_bindgen(method)]
fn f3(x: &&u32);
#[wasm_bindgen(method)]
fn f4(x: &foo::Bar);
#[wasm_bindgen(method)]
fn f4(x: &::Bar);
#[wasm_bindgen(method)]
fn f4(x: &Bar<T>);
#[wasm_bindgen(method)]
fn f4(x: &Fn(T));
#[wasm_bindgen(constructor)]
fn f();
#[wasm_bindgen(constructor)]
fn f() -> ::Bar;
#[wasm_bindgen(constructor)]
fn f() -> &Bar;
#[wasm_bindgen(catch)]
fn f() -> u32;
#[wasm_bindgen(catch)]
fn f() -> &u32;
#[wasm_bindgen(catch)]
fn f() -> Result<>;
#[wasm_bindgen(catch)]
fn f() -> Result<'a>;
}

View File

@ -0,0 +1,92 @@
error: it is currently not sound to use lifetimes in function signatures
--> $DIR/invalid-imports.rs:11:16
|
11 | fn f() -> &'static u32;
| ^^^^^^^
error: imported methods must have at least one argument
--> $DIR/invalid-imports.rs:14:5
|
14 | fn f1();
| ^^^^^^^^
error: first argument of method must be a shared reference
--> $DIR/invalid-imports.rs:16:14
|
16 | fn f2(x: u32);
| ^^^
error: first argument of method must be a path
--> $DIR/invalid-imports.rs:18:14
|
18 | fn f3(x: &&u32);
| ^^^^^
error: multi-segment paths are not supported yet
--> $DIR/invalid-imports.rs:20:15
|
20 | fn f4(x: &foo::Bar);
| ^^^^^^^^
error: global paths are not supported yet
--> $DIR/invalid-imports.rs:22:15
|
22 | fn f4(x: &::Bar);
| ^^^^^
error: paths with type parameters are not supported yet
--> $DIR/invalid-imports.rs:24:15
|
24 | fn f4(x: &Bar<T>);
| ^^^^^^
error: paths with type parameters are not supported yet
--> $DIR/invalid-imports.rs:26:15
|
26 | fn f4(x: &Fn(T));
| ^^^^^
error: constructor returns must be bare types
--> $DIR/invalid-imports.rs:29:5
|
29 | fn f();
| ^^^^^^^
error: global paths are not supported yet
--> $DIR/invalid-imports.rs:31:15
|
31 | fn f() -> ::Bar;
| ^^^^^
error: return value of constructor must be a bare path
--> $DIR/invalid-imports.rs:33:5
|
33 | fn f() -> &Bar;
| ^^^^^^^^^^^^^^^
error: must be Result<...>
--> $DIR/invalid-imports.rs:36:15
|
36 | fn f() -> u32;
| ^^^
error: must be Result<...>
--> $DIR/invalid-imports.rs:38:15
|
38 | fn f() -> &u32;
| ^^^^
error: must have at least one generic parameter
--> $DIR/invalid-imports.rs:40:15
|
40 | fn f() -> Result<>;
| ^^^^^^^^
error: it is currently not sound to use lifetimes in function signatures
--> $DIR/invalid-imports.rs:42:22
|
42 | fn f() -> Result<'a>;
| ^^
error: aborting due to 15 previous errors

View File

@ -0,0 +1,38 @@
#![feature(use_extern_macros)]
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
fn foo() {}
#[wasm_bindgen]
pub unsafe fn foo1() {}
#[wasm_bindgen]
pub const fn foo2() {}
#[wasm_bindgen]
struct Foo<T>(T);
#[wasm_bindgen]
extern "C" {
static mut FOO: u32;
pub fn foo3(x: i32, ...);
}
#[wasm_bindgen]
extern "system" {
}
#[wasm_bindgen]
pub fn foo4<T>() {}
#[wasm_bindgen]
pub fn foo5<'a>() {}
#[wasm_bindgen]
pub fn foo6<'a, T>() {}
#[wasm_bindgen]
trait X {}

View File

@ -0,0 +1,68 @@
error: can only #[wasm_bindgen] public functions
--> $DIR/invalid-items.rs:8:1
|
8 | fn foo() {}
| ^^^^^^^^^^^
error: can only #[wasm_bindgen] safe functions
--> $DIR/invalid-items.rs:11:5
|
11 | pub unsafe fn foo1() {}
| ^^^^^^
error: can only #[wasm_bindgen] non-const functions
--> $DIR/invalid-items.rs:14:5
|
14 | pub const fn foo2() {}
| ^^^^^
error: structs with #[wasm_bindgen] cannot have lifetime or type parameters currently
--> $DIR/invalid-items.rs:17:11
|
17 | struct Foo<T>(T);
| ^^^
error: cannot import mutable globals yet
--> $DIR/invalid-items.rs:21:12
|
21 | static mut FOO: u32;
| ^^^
error: can't #[wasm_bindgen] variadic functions
--> $DIR/invalid-items.rs:23:25
|
23 | pub fn foo3(x: i32, ...);
| ^^^
error: only foreign mods with the `C` ABI are allowed
--> $DIR/invalid-items.rs:27:8
|
27 | extern "system" {
| ^^^^^^^^
error: can't #[wasm_bindgen] functions with lifetime or type parameters
--> $DIR/invalid-items.rs:31:12
|
31 | pub fn foo4<T>() {}
| ^^^
error: can't #[wasm_bindgen] functions with lifetime or type parameters
--> $DIR/invalid-items.rs:33:12
|
33 | pub fn foo5<'a>() {}
| ^^^^
error: can't #[wasm_bindgen] functions with lifetime or type parameters
--> $DIR/invalid-items.rs:35:12
|
35 | pub fn foo6<'a, T>() {}
| ^^^^^^^
error: #[wasm_bindgen] can only be applied to a function, struct, enum, impl, or extern block
--> $DIR/invalid-items.rs:38:1
|
38 | trait X {}
| ^^^^^^^^^^
error: aborting due to 11 previous errors

View File

@ -0,0 +1,43 @@
#![feature(use_extern_macros)]
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct A;
#[wasm_bindgen]
default impl A {
}
#[wasm_bindgen]
unsafe impl A {
}
#[wasm_bindgen]
impl Clone for A {
}
#[wasm_bindgen]
impl<T> A {
}
#[wasm_bindgen]
impl &'static A {
}
macro_rules! x { () => () }
#[wasm_bindgen]
impl A {
const X: u32 = 3;
type Y = u32;
x!();
// pub default fn foo() {} // TODO: compiler's pretty printer totally broken
pub const fn foo() {}
pub unsafe fn foo() {}
}

View File

@ -0,0 +1,62 @@
error: #[wasm_bindgen] default impls are not supported
--> $DIR/invalid-methods.rs:11:1
|
11 | default impl A {
| ^^^^^^^
error: #[wasm_bindgen] unsafe impls are not supported
--> $DIR/invalid-methods.rs:15:1
|
15 | unsafe impl A {
| ^^^^^^
error: #[wasm_bindgen] trait impls are not supported
--> $DIR/invalid-methods.rs:19:6
|
19 | impl Clone for A {
| ^^^^^
error: #[wasm_bindgen] generic impls aren't supported
--> $DIR/invalid-methods.rs:23:5
|
23 | impl<T> A {
| ^^^
error: unsupported self type in #[wasm_bindgen] impl
--> $DIR/invalid-methods.rs:27:6
|
27 | impl &'static A {
| ^^^^^^^^^^
error: const definitions aren't supported with #[wasm_bindgen]
--> $DIR/invalid-methods.rs:34:5
|
34 | const X: u32 = 3;
| ^^^^^^^^^^^^^^^^^
error: type definitions in impls aren't supported with #[wasm_bindgen]
--> $DIR/invalid-methods.rs:35:5
|
35 | type Y = u32;
| ^^^^^^^^^^^^^
error: macros in impls aren't supported
--> $DIR/invalid-methods.rs:36:5
|
36 | x!();
| ^^^^^
error: can only #[wasm_bindgen] non-const functions
--> $DIR/invalid-methods.rs:41:9
|
41 | pub const fn foo() {}
| ^^^^^
error: can only bindgen safe functions
--> $DIR/invalid-methods.rs:42:9
|
42 | pub unsafe fn foo() {}
| ^^^^^^
error: aborting due to 10 previous errors

View File

@ -1,10 +1,8 @@
error: custom attribute panicked error: can only #[wasm_bindgen] public functions
--> $DIR/non-public-function.rs:7:1 --> $DIR/non-public-function.rs:8:1
| |
7 | #[wasm_bindgen] 8 | fn foo() {}
| ^^^^^^^^^^^^^^^ | ^^^^^^^^^^^
|
= help: message: can only bindgen public functions
error: aborting due to previous error error: aborting due to previous error