Merge pull request #1055 from alexcrichton/strict

Assert all attributes are used by default
This commit is contained in:
Alex Crichton 2018-11-28 13:43:04 -06:00 committed by GitHub
commit f4c1c64078
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 355 additions and 361 deletions

View File

@ -30,6 +30,10 @@ std = []
serde-serialize = ["serde", "serde_json", "std"]
nightly = []
# Whether or not the `#[wasm_bindgen]` macro is strict and generates an error on
# all unused attributes
strict-macro = ["wasm-bindgen-macro/strict-macro"]
# This is only for debugging wasm-bindgen! No stability guarantees, so enable
# this at your own peril!
xxx_debug_only_print_generated_code = ["wasm-bindgen-macro/xxx_debug_only_print_generated_code"]

View File

@ -5,7 +5,7 @@ use syn::parse::Error;
#[macro_export]
macro_rules! err_span {
($span:expr, $($msg:tt)*) => (
$crate::Diagnostic::span_error(&$span, format!($($msg)*))
$crate::Diagnostic::spanned_error(&$span, format!($($msg)*))
)
}
@ -43,7 +43,16 @@ impl Diagnostic {
}
}
pub fn span_error<T: Into<String>>(node: &ToTokens, text: T) -> Diagnostic {
pub fn span_error<T: Into<String>>(span: Span, text: T) -> Diagnostic {
Diagnostic {
inner: Repr::Single {
text: text.into(),
span: Some((span, span)),
},
}
}
pub fn spanned_error<T: Into<String>>(node: &ToTokens, text: T) -> Diagnostic {
Diagnostic {
inner: Repr::Single {
text: text.into(),

View File

@ -13,6 +13,7 @@ The part of the implementation of the `#[wasm_bindgen]` attribute that is not in
[features]
spans = ["wasm-bindgen-backend/spans"]
extra-traits = ["syn/extra-traits"]
strict-macro = []
[dependencies]
syn = { version = '0.15.0', features = ['visit'] }

View File

@ -20,6 +20,7 @@ mod parser;
/// Takes the parsed input from a `#[wasm_bindgen]` macro and returns the generated bindings
pub fn expand(attr: TokenStream, input: TokenStream) -> Result<TokenStream, Diagnostic> {
parser::reset_attrs_used();
let item = syn::parse2::<syn::Item>(input)?;
let opts = syn::parse2(attr)?;
@ -27,5 +28,11 @@ pub fn expand(attr: TokenStream, input: TokenStream) -> Result<TokenStream, Diag
let mut program = backend::ast::Program::default();
item.macro_parse(&mut program, (Some(opts), &mut tokens))?;
program.try_to_tokens(&mut tokens)?;
// If we successfully got here then we should have used up all attributes
// and considered all of them to see if they were used. If one was forgotten
// that's a bug on our end, so sanity check here.
parser::assert_all_attrs_checked();
Ok(tokens)
}

View File

@ -1,3 +1,5 @@
use std::cell::Cell;
use backend::ast;
use backend::util::{ident_ty, ShortHash};
use backend::Diagnostic;
@ -7,356 +9,256 @@ use shared;
use syn;
use syn::parse::{Parse, ParseStream, Result as SynResult};
thread_local!(static ATTRS: AttributeParseState = Default::default());
#[derive(Default)]
struct AttributeParseState {
parsed: Cell<usize>,
checks: Cell<usize>,
}
/// 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>,
pub attrs: Vec<(Cell<bool>, BindgenAttr)>,
}
macro_rules! attrgen {
($mac:ident) => (
$mac! {
(catch, Catch(Span)),
(constructor, Constructor(Span)),
(method, Method(Span)),
(static_method_of, StaticMethodOf(Span, Ident)),
(js_namespace, JsNamespace(Span, Ident)),
(module, Module(Span, String, Span)),
(getter, Getter(Span, Option<Ident>)),
(setter, Setter(Span, Option<Ident>)),
(indexing_getter, IndexingGetter(Span)),
(indexing_setter, IndexingSetter(Span)),
(indexing_deleter, IndexingDeleter(Span)),
(structural, Structural(Span)),
(final_("final"), Final(Span)),
(readonly, Readonly(Span)),
(js_name, JsName(Span, String, Span)),
(js_class, JsClass(Span, String, Span)),
(extends, Extends(Span, syn::Path)),
(vendor_prefix, VendorPrefix(Span, Ident)),
(variadic, Variadic(Span)),
(typescript_custom_section, TypescriptCustomSection(Span)),
}
)
}
macro_rules! methods {
($(($name:ident $(($other:tt))*, $variant:ident($($contents:tt)*)),)*) => {
$(methods!(@method $name, $variant($($contents)*));)*
fn check_used(self) -> Result<(), Diagnostic> {
// Account for the fact this method was called
ATTRS.with(|state| state.checks.set(state.checks.get() + 1));
let mut errors = Vec::new();
for (used, attr) in self.attrs.iter() {
if used.get() {
continue
}
if !cfg!(feature = "strict-macro") {
continue
}
let span = match attr {
$(BindgenAttr::$variant(span, ..) => span,)*
};
errors.push(Diagnostic::span_error(*span, "unused #[wasm_bindgen] attribute"));
}
Diagnostic::from_vec(errors)
}
};
(@method $name:ident, $variant:ident(Span, String, Span)) => {
fn $name(&self) -> Option<(&str, Span)> {
self.attrs
.iter()
.filter_map(|a| match &a.1 {
BindgenAttr::$variant(_, s, span) => {
a.0.set(true);
Some((&s[..], *span))
}
_ => None,
})
.next()
}
};
(@method $name:ident, $variant:ident(Span, $($other:tt)*)) => {
#[allow(unused)]
fn $name(&self) -> Option<&$($other)*> {
self.attrs
.iter()
.filter_map(|a| match &a.1 {
BindgenAttr::$variant(_, s) => {
a.0.set(true);
Some(s)
}
_ => None,
})
.next()
}
};
(@method $name:ident, $variant:ident($($other:tt)*)) => {
#[allow(unused)]
fn $name(&self) -> Option<&$($other)*> {
self.attrs
.iter()
.filter_map(|a| match &a.1 {
BindgenAttr::$variant(s) => {
a.0.set(true);
Some(s)
}
_ => None,
})
.next()
}
};
}
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");
let mut ret = BindgenAttrs::default();
loop {
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(ret),
};
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 => continue,
};
if tts.next().is_some() {
bail_span!(attr, "malformed #[wasm_bindgen] attribute");
}
if group.delimiter() != Delimiter::Parenthesis {
bail_span!(attr, "malformed #[wasm_bindgen] attribute");
}
let mut attrs: BindgenAttrs = syn::parse2(group.stream())?;
ret.attrs.extend(attrs.attrs.drain(..));
attrs.check_used()?;
}
if group.delimiter() != Delimiter::Parenthesis {
bail_span!(attr, "malformed #[wasm_bindgen] attribute");
}
Ok(syn::parse2(group.stream())?)
}
/// 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()
}
attrgen!(methods);
}
/// 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()
}
/// Whether the indexing getter attributes is present
fn indexing_getter(&self) -> bool {
self.attrs.iter().any(|a| match *a {
BindgenAttr::IndexingGetter => true,
_ => false,
})
}
/// Whether the indexing setter attributes is present
fn indexing_setter(&self) -> bool {
self.attrs.iter().any(|a| match *a {
BindgenAttr::IndexingSetter => true,
_ => false,
})
}
/// Whether the indexing deleter attributes is present
fn indexing_deleter(&self) -> bool {
self.attrs.iter().any(|a| match *a {
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 `final` attribute is present
fn final_(&self) -> Option<&Ident> {
self.attrs
.iter()
.filter_map(|a| match a {
BindgenAttr::Final(i) => Some(i),
_ => None,
})
.next()
}
/// 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, Span)> {
self.attrs
.iter()
.filter_map(|a| match a {
BindgenAttr::JsName(s, span) => Some((&s[..], *span)),
_ => 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 = &syn::Path> {
self.attrs.iter().filter_map(|a| match a {
BindgenAttr::Extends(s) => Some(s),
_ => None,
})
}
fn vendor_prefixes(&self) -> impl Iterator<Item = &Ident> {
self.attrs.iter().filter_map(|a| match a {
BindgenAttr::VendorPrefix(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,
})
}
fn typescript_custom_section(&self) -> bool {
self.attrs.iter().any(|a| match *a {
BindgenAttr::TypescriptCustomSection => true,
_ => false,
})
impl Default for BindgenAttrs {
fn default() -> BindgenAttrs {
// Add 1 to the list of parsed attribute sets. We'll use this counter to
// sanity check that we call `check_used` an appropriate number of
// times.
ATTRS.with(|state| state.parsed.set(state.parsed.get() + 1));
BindgenAttrs { attrs: Vec::new() }
}
}
impl Parse for BindgenAttrs {
fn parse(input: ParseStream) -> SynResult<Self> {
let mut attrs = BindgenAttrs::default();
if input.is_empty() {
return Ok(BindgenAttrs { attrs: Vec::new() });
return Ok(attrs)
}
let opts = syn::punctuated::Punctuated::<_, syn::token::Comma>::parse_terminated(input)?;
Ok(BindgenAttrs {
attrs: opts.into_iter().collect(),
})
attrs.attrs = opts
.into_iter()
.map(|c| (Cell::new(false), c))
.collect();
Ok(attrs)
}
}
/// 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>),
IndexingGetter,
IndexingSetter,
IndexingDeleter,
Structural,
Final(Ident),
Readonly,
JsName(String, Span),
JsClass(String),
Extends(syn::Path),
VendorPrefix(Ident),
Variadic,
TypescriptCustomSection,
macro_rules! gen_bindgen_attr {
($(($method:ident $(($other:tt))*, $($variants:tt)*),)*) => {
/// The possible attributes in the `#[wasm_bindgen]`.
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
pub enum BindgenAttr {
$($($variants)*,)*
}
}
}
attrgen!(gen_bindgen_attr);
impl Parse for BindgenAttr {
fn parse(input: ParseStream) -> SynResult<Self> {
let original = input.fork();
let attr: AnyIdent = input.parse()?;
let attr = attr.0;
if attr == "catch" {
return Ok(BindgenAttr::Catch);
}
if attr == "constructor" {
return Ok(BindgenAttr::Constructor);
}
if attr == "method" {
return Ok(BindgenAttr::Method);
}
if attr == "indexing_getter" {
return Ok(BindgenAttr::IndexingGetter);
}
if attr == "indexing_setter" {
return Ok(BindgenAttr::IndexingSetter);
}
if attr == "indexing_deleter" {
return Ok(BindgenAttr::IndexingDeleter);
}
if attr == "structural" {
return Ok(BindgenAttr::Structural);
}
if attr == "final" {
return Ok(BindgenAttr::Final(attr));
}
if attr == "readonly" {
return Ok(BindgenAttr::Readonly);
}
if attr == "variadic" {
return Ok(BindgenAttr::Variadic);
}
if attr == "static_method_of" {
input.parse::<Token![=]>()?;
return Ok(BindgenAttr::StaticMethodOf(input.parse::<AnyIdent>()?.0));
}
if attr == "getter" {
if input.parse::<Token![=]>().is_ok() {
return Ok(BindgenAttr::Getter(Some(input.parse::<AnyIdent>()?.0)));
} else {
return Ok(BindgenAttr::Getter(None));
}
}
if attr == "setter" {
if input.parse::<Token![=]>().is_ok() {
return Ok(BindgenAttr::Setter(Some(input.parse::<AnyIdent>()?.0)));
} else {
return Ok(BindgenAttr::Setter(None));
}
}
if attr == "js_namespace" {
input.parse::<Token![=]>()?;
return Ok(BindgenAttr::JsNamespace(input.parse::<AnyIdent>()?.0));
}
if attr == "extends" {
input.parse::<Token![=]>()?;
return Ok(BindgenAttr::Extends(input.parse()?));
}
if attr == "vendor_prefix" {
input.parse::<Token![=]>()?;
return Ok(BindgenAttr::VendorPrefix(input.parse::<AnyIdent>()?.0));
}
if attr == "module" {
input.parse::<Token![=]>()?;
return Ok(BindgenAttr::Module(input.parse::<syn::LitStr>()?.value()));
}
if attr == "js_class" {
input.parse::<Token![=]>()?;
let val = match input.parse::<syn::LitStr>() {
Ok(str) => str.value(),
Err(_) => input.parse::<AnyIdent>()?.0.to_string(),
let attr_span = attr.span();
macro_rules! parsers {
($(($name:ident $(($other:tt))*, $($contents:tt)*),)*) => {
$(
if attr == parsers!(@attrname $name $($other)*) {
parsers!(
@parser
$($contents)*
);
}
)*
};
return Ok(BindgenAttr::JsClass(val));
}
if attr == "js_name" {
input.parse::<Token![=]>()?;
let (val, span) = match input.parse::<syn::LitStr>() {
Ok(str) => (str.value(), str.span()),
Err(_) => {
(@parser $variant:ident(Span)) => ({
return Ok(BindgenAttr::$variant(attr_span));
});
(@parser $variant:ident(Span, Ident)) => ({
input.parse::<Token![=]>()?;
let ident = input.parse::<AnyIdent>()?.0;
return Ok(BindgenAttr::$variant(attr_span, ident))
});
(@parser $variant:ident(Span, Option<Ident>)) => ({
if input.parse::<Token![=]>().is_ok() {
let ident = input.parse::<AnyIdent>()?.0;
(ident.to_string(), ident.span())
return Ok(BindgenAttr::$variant(attr_span, Some(ident)))
} else {
return Ok(BindgenAttr::$variant(attr_span, None));
}
};
return Ok(BindgenAttr::JsName(val, span));
}
if attr == "typescript_custom_section" {
return Ok(BindgenAttr::TypescriptCustomSection);
});
(@parser $variant:ident(Span, syn::Path)) => ({
input.parse::<Token![=]>()?;
return Ok(BindgenAttr::$variant(attr_span, input.parse()?));
});
(@parser $variant:ident(Span, String, Span)) => ({
input.parse::<Token![=]>()?;
let (val, span) = match input.parse::<syn::LitStr>() {
Ok(str) => (str.value(), str.span()),
Err(_) => {
let ident = input.parse::<AnyIdent>()?.0;
(ident.to_string(), ident.span())
}
};
return Ok(BindgenAttr::$variant(attr_span, val, span))
});
(@attrname $a:ident $b:tt) => ($b);
(@attrname $a:ident) => (stringify!($a));
}
Err(original.error("unknown attribute"))
attrgen!(parsers);
return Err(original.error("unknown attribute"));
}
}
@ -414,20 +316,22 @@ impl<'a> ConvertToAst<BindgenAttrs> for &'a mut syn::ItemStruct {
let getter = shared::struct_field_get(&js_name, &name_str);
let setter = shared::struct_field_set(&js_name, &name_str);
let opts = BindgenAttrs::find(&mut field.attrs)?;
assert_not_variadic(&opts, &field)?;
assert_not_variadic(&opts)?;
let comments = extract_doc_comments(&field.attrs);
fields.push(ast::StructField {
name: name.clone(),
struct_name: self.ident.clone(),
readonly: opts.readonly(),
readonly: opts.readonly().is_some(),
ty: field.ty.clone(),
getter: Ident::new(&getter, Span::call_site()),
setter: Ident::new(&setter, Span::call_site()),
comments,
});
opts.check_used()?;
}
}
let comments: Vec<String> = extract_doc_comments(&self.attrs);
opts.check_used()?;
Ok(ast::Struct {
rust_name: self.ident.clone(),
js_name,
@ -454,8 +358,8 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn
None,
)?
.0;
let catch = opts.catch();
let variadic = opts.variadic();
let catch = opts.catch().is_some();
let variadic = opts.variadic().is_some();
let js_ret = if catch {
// TODO: this assumes a whole bunch:
//
@ -471,22 +375,22 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn
let mut operation_kind = ast::OperationKind::Regular;
if let Some(g) = opts.getter() {
operation_kind = ast::OperationKind::Getter(g);
operation_kind = ast::OperationKind::Getter(g.clone());
}
if let Some(s) = opts.setter() {
operation_kind = ast::OperationKind::Setter(s);
operation_kind = ast::OperationKind::Setter(s.clone());
}
if opts.indexing_getter() {
if opts.indexing_getter().is_some() {
operation_kind = ast::OperationKind::IndexingGetter;
}
if opts.indexing_setter() {
if opts.indexing_setter().is_some() {
operation_kind = ast::OperationKind::IndexingSetter;
}
if opts.indexing_deleter() {
if opts.indexing_deleter().is_some() {
operation_kind = ast::OperationKind::IndexingDeleter;
}
let kind = if opts.method() {
let kind = if opts.method().is_some() {
let class = wasm.arguments.get(0).ok_or_else(|| {
err_span!(self, "imported methods must have at least one argument")
})?;
@ -511,7 +415,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn
let class_name = extract_path_ident(class_name)?;
let class_name = opts
.js_class()
.map(Into::into)
.map(|p| p.0.into())
.unwrap_or_else(|| class_name.to_string());
let kind = ast::MethodKind::Operation(ast::Operation {
@ -527,7 +431,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn
} else if let Some(cls) = opts.static_method_of() {
let class = opts
.js_class()
.map(Into::into)
.map(|p| p.0.into())
.unwrap_or_else(|| cls.to_string());
let ty = ident_ty(cls.clone());
@ -537,7 +441,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn
});
ast::ImportFunctionKind::Method { class, ty, kind }
} else if opts.constructor() {
} else if opts.constructor().is_some() {
let class = match js_ret {
Some(ref ty) => ty,
_ => bail_span!(self, "constructor returns must be bare types"),
@ -552,7 +456,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn
let class_name = extract_path_ident(class_name)?;
let class_name = opts
.js_class()
.map(Into::into)
.map(|p| p.0.into())
.unwrap_or_else(|| class_name.to_string());
ast::ImportFunctionKind::Method {
@ -579,22 +483,26 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn
ShortHash(data)
)
};
if let Some(ident) = opts.final_() {
if opts.structural() {
bail_span!(ident, "cannot specify both `structural` and `final`");
if let Some(span) = opts.final_() {
if opts.structural().is_some() {
let msg = "cannot specify both `structural` and `final`";
return Err(Diagnostic::span_error(*span, msg))
}
}
Ok(ast::ImportKind::Function(ast::ImportFunction {
let ret = ast::ImportKind::Function(ast::ImportFunction {
function: wasm,
kind,
js_ret,
catch,
variadic,
structural: opts.structural() || opts.final_().is_none(),
structural: opts.structural().is_some() || opts.final_().is_none(),
rust_name: self.ident.clone(),
shim: Ident::new(&shim, Span::call_site()),
doc_comment: None,
}))
});
opts.check_used()?;
Ok(ret)
}
}
@ -602,12 +510,28 @@ impl ConvertToAst<BindgenAttrs> for syn::ForeignItemType {
type Target = ast::ImportKind;
fn convert(self, attrs: BindgenAttrs) -> Result<Self::Target, Diagnostic> {
assert_not_variadic(&attrs, &self)?;
assert_not_variadic(&attrs)?;
let js_name = attrs
.js_name()
.map(|s| s.0)
.map_or_else(|| self.ident.to_string(), |s| s.to_string());
let shim = format!("__wbg_instanceof_{}_{}", self.ident, ShortHash(&self.ident));
let mut extends = Vec::new();
let mut vendor_prefixes = Vec::new();
for (used, attr) in attrs.attrs.iter() {
match attr {
BindgenAttr::Extends(_, e) => {
extends.push(e.clone());
used.set(true);
}
BindgenAttr::VendorPrefix(_, e) => {
vendor_prefixes.push(e.clone());
used.set(true);
}
_ => {}
}
}
attrs.check_used()?;
Ok(ast::ImportKind::Type(ast::ImportType {
vis: self.vis,
attrs: self.attrs,
@ -615,8 +539,8 @@ impl ConvertToAst<BindgenAttrs> for syn::ForeignItemType {
instanceof_shim: shim,
rust_name: self.ident,
js_name,
extends: attrs.extends().cloned().collect(),
vendor_prefixes: attrs.vendor_prefixes().cloned().collect(),
extends,
vendor_prefixes,
}))
}
}
@ -631,19 +555,20 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemSt
if self.mutability.is_some() {
bail_span!(self.mutability, "cannot import mutable globals yet")
}
assert_not_variadic(&opts, &self)?;
assert_not_variadic(&opts)?;
let default_name = self.ident.to_string();
let js_name = opts.js_name().map(|p| p.0).unwrap_or(&default_name);
let js_name = opts.js_name().map(|p| p.0).unwrap_or(&default_name).to_string();
let shim = format!(
"__wbg_static_accessor_{}_{}",
self.ident,
ShortHash((&js_name, module, &self.ident)),
);
opts.check_used()?;
Ok(ast::ImportKind::Static(ast::ImportStatic {
ty: *self.ty,
vis: self.vis,
rust_name: self.ident.clone(),
js_name: js_name.to_string(),
js_name,
shim: Ident::new(&shim, Span::call_site()),
}))
}
@ -666,9 +591,9 @@ impl ConvertToAst<BindgenAttrs> for syn::ItemFn {
if self.unsafety.is_some() {
bail_span!(self.unsafety, "can only #[wasm_bindgen] safe functions");
}
assert_not_variadic(&attrs, &self)?;
assert_not_variadic(&attrs)?;
Ok(function_from_decl(
let ret = function_from_decl(
&self.ident,
&attrs,
self.decl,
@ -676,8 +601,9 @@ impl ConvertToAst<BindgenAttrs> for syn::ItemFn {
self.vis,
false,
None,
)?
.0)
)?;
attrs.check_used()?;
Ok(ret.0)
}
}
@ -832,6 +758,9 @@ impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
f.macro_parse(program, opts)?;
}
syn::Item::Enum(e) => {
if let Some(opts) = opts {
opts.check_used()?;
}
e.to_tokens(tokens);
e.macro_parse(program, ())?;
}
@ -894,7 +823,9 @@ impl<'a> MacroParse<BindgenAttrs> for &'a mut syn::ItemImpl {
errors.push(e);
}
}
Diagnostic::from_vec(errors)
Diagnostic::from_vec(errors)?;
opts.check_used()?;
Ok(())
}
}
@ -945,7 +876,7 @@ impl<'a, 'b> MacroParse<&'a BindgenAttrs> for (&'a Ident, &'b mut syn::ImplItem)
let opts = BindgenAttrs::find(&mut method.attrs)?;
let comments = extract_doc_comments(&method.attrs);
let is_constructor = opts.constructor();
let is_constructor = opts.constructor().is_some();
let (function, method_self) = function_from_decl(
&method.sig.ident,
&opts,
@ -957,7 +888,7 @@ impl<'a, 'b> MacroParse<&'a BindgenAttrs> for (&'a Ident, &'b mut syn::ImplItem)
)?;
let js_class = impl_opts
.js_class()
.map(|s| s.to_string())
.map(|s| s.0.to_string())
.unwrap_or(class.to_string());
program.exports.push(ast::Export {
@ -969,6 +900,7 @@ impl<'a, 'b> MacroParse<&'a BindgenAttrs> for (&'a Ident, &'b mut syn::ImplItem)
comments,
rust_name: method.sig.ident.clone(),
});
opts.check_used()?;
Ok(())
}
}
@ -1033,7 +965,7 @@ impl MacroParse<()> for syn::ItemEnum {
impl MacroParse<BindgenAttrs> for syn::ItemConst {
fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> {
// Shortcut
if !opts.typescript_custom_section() {
if opts.typescript_custom_section().is_none() {
bail_span!(self, "#[wasm_bindgen] will not work on constants unless you are defining a #[wasm_bindgen(typescript_custom_section)].");
}
@ -1049,6 +981,8 @@ impl MacroParse<BindgenAttrs> for syn::ItemConst {
}
}
opts.check_used()?;
Ok(())
}
}
@ -1071,7 +1005,9 @@ impl MacroParse<BindgenAttrs> for syn::ItemForeignMod {
errors.push(e);
}
}
Diagnostic::from_vec(errors)
Diagnostic::from_vec(errors)?;
opts.check_used()?;
Ok(())
}
}
@ -1090,7 +1026,7 @@ impl<'a> MacroParse<&'a BindgenAttrs> for syn::ForeignItem {
};
BindgenAttrs::find(attrs)?
};
let module = item_opts.module().or(opts.module()).map(|s| s.to_string());
let module = item_opts.module().or(opts.module()).map(|s| s.0.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))?,
@ -1200,13 +1136,11 @@ fn assert_no_lifetimes(decl: &syn::FnDecl) -> Result<(), Diagnostic> {
}
/// This method always fails if the BindgenAttrs contain variadic
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"
)
fn assert_not_variadic(attrs: &BindgenAttrs) -> Result<(), Diagnostic> {
if let Some(span) = attrs.variadic() {
let msg = "the `variadic` attribute can only be applied to imported \
(`extern`) functions";
return Err(Diagnostic::span_error(*span, msg))
}
Ok(())
}
@ -1226,3 +1160,16 @@ fn extract_path_ident(path: &syn::Path) -> Result<Ident, Diagnostic> {
}
Ok(value.ident.clone())
}
pub fn reset_attrs_used() {
ATTRS.with(|state| {
state.parsed.set(0);
state.checks.set(0);
})
}
pub fn assert_all_attrs_checked() {
ATTRS.with(|state| {
assert_eq!(state.parsed.get(), state.checks.get());
})
}

View File

@ -16,6 +16,7 @@ proc-macro = true
[features]
spans = ["wasm-bindgen-macro-support/spans"]
xxx_debug_only_print_generated_code = []
strict-macro = ["wasm-bindgen-macro-support/strict-macro"]
[dependencies]
wasm-bindgen-macro-support = { path = "../macro-support", version = "=0.2.28" }

View File

@ -9,7 +9,7 @@ doctest = false
harness = false
[dependencies]
wasm-bindgen = { path = "../../.." }
wasm-bindgen = { path = "../../..", features = ["strict-macro"] }
[dev-dependencies]
compiletest_rs = "0.3"

View File

@ -0,0 +1,11 @@
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
impl A {
#[wasm_bindgen(method)]
#[wasm_bindgen(method)]
pub fn foo() {
}
}

View File

@ -0,0 +1,14 @@
error: unused #[wasm_bindgen] attribute
--> $DIR/unused-attributes.rs:7:20
|
7 | #[wasm_bindgen(method)]
| ^^^^^^
error: unused #[wasm_bindgen] attribute
--> $DIR/unused-attributes.rs:8:20
|
8 | #[wasm_bindgen(method)]
| ^^^^^^
error: aborting due to 2 previous errors

View File

@ -32,7 +32,8 @@ extern "C" {
fn switch_methods_a();
fn switch_methods_b();
type SwitchMethods;
#[wasm_bindgen(constructor, final)]
#[wasm_bindgen(constructor)]
#[wasm_bindgen(final)]
fn new() -> SwitchMethods;
#[wasm_bindgen(js_namespace = SwitchMethods, final)]
fn a();

View File

@ -13,7 +13,6 @@ pub struct Fruit {
#[wasm_bindgen]
impl Fruit {
#[wasm_bindgen(method)]
pub fn name(&self) -> String {
self.name.clone()
}