From 48d3b749a8737eaf0d8eae113d2f6482a694afc4 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 27 Nov 2018 15:14:59 -0800 Subject: [PATCH] Assert all attributes are used by default This commit implements a system that will assert that all `#[wasm_bindgen]` attributes are actually used during compilation. This should help ensure that we don't sneak in stray attributes that don't actually end up having any meaning, and hopefully make it a bit easier to learn `#[wasm_bindgen]`! --- Cargo.toml | 6 +- crates/backend/src/error.rs | 13 +- crates/macro-support/Cargo.toml | 1 + crates/macro-support/src/lib.rs | 7 + crates/macro-support/src/parser.rs | 610 +++++++++++++---------------- crates/macro/Cargo.toml | 1 + tests/wasm/validate_prt.rs | 1 - 7 files changed, 301 insertions(+), 338 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4e2252bb79e6..2597cca4228e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,12 +24,16 @@ test = false doctest = false [features] -default = ["spans", "std"] +default = ["spans", "std", 'strict-macro'] spans = ["wasm-bindgen-macro/spans"] 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"] diff --git a/crates/backend/src/error.rs b/crates/backend/src/error.rs index 654c6093d514..0154675782ab 100644 --- a/crates/backend/src/error.rs +++ b/crates/backend/src/error.rs @@ -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>(node: &ToTokens, text: T) -> Diagnostic { + pub fn span_error>(span: Span, text: T) -> Diagnostic { + Diagnostic { + inner: Repr::Single { + text: text.into(), + span: Some((span, span)), + }, + } + } + + pub fn spanned_error>(node: &ToTokens, text: T) -> Diagnostic { Diagnostic { inner: Repr::Single { text: text.into(), diff --git a/crates/macro-support/Cargo.toml b/crates/macro-support/Cargo.toml index 5980745277ae..27bd57f414bc 100644 --- a/crates/macro-support/Cargo.toml +++ b/crates/macro-support/Cargo.toml @@ -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'] } diff --git a/crates/macro-support/src/lib.rs b/crates/macro-support/src/lib.rs index e702e6a3c391..b931bc8259c4 100644 --- a/crates/macro-support/src/lib.rs +++ b/crates/macro-support/src/lib.rs @@ -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 { + parser::reset_attrs_used(); let item = syn::parse2::(input)?; let opts = syn::parse2(attr)?; @@ -27,5 +28,11 @@ pub fn expand(attr: TokenStream, input: TokenStream) -> Result, + checks: Cell, +} + /// 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, + pub attrs: Vec<(Cell, 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)), + (setter, Setter(Span, Option)), + (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 { @@ -43,320 +152,108 @@ impl BindgenAttrs { 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() - } - - /// 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> { - 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> { - 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 { - self.attrs.iter().filter_map(|a| match a { - BindgenAttr::Extends(s) => Some(s), - _ => None, - }) - } - - fn vendor_prefixes(&self) -> impl Iterator { - 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, - }) - } + attrgen!(methods); +} - 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 { + 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), - Setter(Option), - 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 { 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::()?; - return Ok(BindgenAttr::StaticMethodOf(input.parse::()?.0)); - } - if attr == "getter" { - if input.parse::().is_ok() { - return Ok(BindgenAttr::Getter(Some(input.parse::()?.0))); - } else { - return Ok(BindgenAttr::Getter(None)); - } - } - if attr == "setter" { - if input.parse::().is_ok() { - return Ok(BindgenAttr::Setter(Some(input.parse::()?.0))); - } else { - return Ok(BindgenAttr::Setter(None)); - } - } - if attr == "js_namespace" { - input.parse::()?; - return Ok(BindgenAttr::JsNamespace(input.parse::()?.0)); - } - if attr == "extends" { - input.parse::()?; - return Ok(BindgenAttr::Extends(input.parse()?)); - } - if attr == "vendor_prefix" { - input.parse::()?; - return Ok(BindgenAttr::VendorPrefix(input.parse::()?.0)); - } - if attr == "module" { - input.parse::()?; - return Ok(BindgenAttr::Module(input.parse::()?.value())); - } - if attr == "js_class" { - input.parse::()?; - let val = match input.parse::() { - Ok(str) => str.value(), - Err(_) => input.parse::()?.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::()?; - let (val, span) = match input.parse::() { - Ok(str) => (str.value(), str.span()), - Err(_) => { + + (@parser $variant:ident(Span)) => ({ + return Ok(BindgenAttr::$variant(attr_span)); + }); + + (@parser $variant:ident(Span, Ident)) => ({ + input.parse::()?; + let ident = input.parse::()?.0; + return Ok(BindgenAttr::$variant(attr_span, ident)) + }); + + (@parser $variant:ident(Span, Option)) => ({ + if input.parse::().is_ok() { let ident = input.parse::()?.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::()?; + return Ok(BindgenAttr::$variant(attr_span, input.parse()?)); + }); + + (@parser $variant:ident(Span, String, Span)) => ({ + input.parse::()?; + let (val, span) = match input.parse::() { + Ok(str) => (str.value(), str.span()), + Err(_) => { + let ident = input.parse::()?.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 +311,22 @@ impl<'a> ConvertToAst 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 = extract_doc_comments(&self.attrs); + opts.check_used()?; Ok(ast::Struct { rust_name: self.ident.clone(), js_name, @@ -454,8 +353,8 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option)> 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 +370,22 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option)> 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 +410,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option)> 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 +426,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option)> 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 +436,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option)> 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 +451,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option)> 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 +478,26 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option)> 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_some(), rust_name: self.ident.clone(), shim: Ident::new(&shim, Span::call_site()), doc_comment: None, - })) + }); + opts.check_used()?; + + Ok(ret) } } @@ -602,12 +505,28 @@ impl ConvertToAst for syn::ForeignItemType { type Target = ast::ImportKind; fn convert(self, attrs: BindgenAttrs) -> Result { - 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 +534,8 @@ impl ConvertToAst 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 +550,20 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option)> 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 +586,9 @@ impl ConvertToAst 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 +596,9 @@ impl ConvertToAst for syn::ItemFn { self.vis, false, None, - )? - .0) + )?; + attrs.check_used()?; + Ok(ret.0) } } @@ -832,6 +753,9 @@ impl<'a> MacroParse<(Option, &'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 +818,9 @@ impl<'a> MacroParse for &'a mut syn::ItemImpl { errors.push(e); } } - Diagnostic::from_vec(errors) + Diagnostic::from_vec(errors)?; + opts.check_used()?; + Ok(()) } } @@ -945,7 +871,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 +883,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 +895,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 +960,7 @@ impl MacroParse<()> for syn::ItemEnum { impl MacroParse 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 +976,8 @@ impl MacroParse for syn::ItemConst { } } + opts.check_used()?; + Ok(()) } } @@ -1071,7 +1000,9 @@ impl MacroParse for syn::ItemForeignMod { errors.push(e); } } - Diagnostic::from_vec(errors) + Diagnostic::from_vec(errors)?; + opts.check_used()?; + Ok(()) } } @@ -1090,7 +1021,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 +1131,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 +1155,16 @@ fn extract_path_ident(path: &syn::Path) -> Result { } 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()); + }) +} diff --git a/crates/macro/Cargo.toml b/crates/macro/Cargo.toml index 30f015252554..4a70286127a5 100644 --- a/crates/macro/Cargo.toml +++ b/crates/macro/Cargo.toml @@ -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" } diff --git a/tests/wasm/validate_prt.rs b/tests/wasm/validate_prt.rs index efae983054c1..42f9698a5e61 100644 --- a/tests/wasm/validate_prt.rs +++ b/tests/wasm/validate_prt.rs @@ -13,7 +13,6 @@ pub struct Fruit { #[wasm_bindgen] impl Fruit { - #[wasm_bindgen(method)] pub fn name(&self) -> String { self.name.clone() }