| diff --git a/src/errors.rs b/src/errors.rs |
| index cfad383..fc5b584 100644 |
| --- a/src/errors.rs |
| +++ b/src/errors.rs |
| @@ -14,15 +14,15 @@ pub struct Errors { |
| errors: RefCell<Vec<syn::Error>>, |
| } |
| |
| -/// Produce functions to expect particular variants of `syn::Lit` |
| +/// Produce functions to expect particular literals in `syn::Expr` |
| macro_rules! expect_lit_fn { |
| ($(($fn_name:ident, $syn_type:ident, $variant:ident, $lit_name:literal),)*) => { |
| $( |
| - pub fn $fn_name<'a>(&self, lit: &'a syn::Lit) -> Option<&'a syn::$syn_type> { |
| - if let syn::Lit::$variant(inner) = lit { |
| + pub fn $fn_name<'a>(&self, e: &'a syn::Expr) -> Option<&'a syn::$syn_type> { |
| + if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::$variant(inner), .. }) = e { |
| Some(inner) |
| } else { |
| - self.unexpected_lit($lit_name, lit); |
| + self.unexpected_lit($lit_name, e); |
| None |
| } |
| } |
| @@ -65,28 +65,6 @@ impl Errors { |
| self.err_span(first, &["First ", attr_kind, " attribute here"].concat()); |
| } |
| |
| - /// Error on literals, expecting attribute syntax. |
| - pub fn expect_nested_meta<'a>(&self, nm: &'a syn::NestedMeta) -> Option<&'a syn::Meta> { |
| - match nm { |
| - syn::NestedMeta::Lit(l) => { |
| - self.err(l, "Unexpected literal"); |
| - None |
| - } |
| - syn::NestedMeta::Meta(m) => Some(m), |
| - } |
| - } |
| - |
| - /// Error on attribute syntax, expecting literals |
| - pub fn expect_nested_lit<'a>(&self, nm: &'a syn::NestedMeta) -> Option<&'a syn::Lit> { |
| - match nm { |
| - syn::NestedMeta::Meta(m) => { |
| - self.err(m, "Expected literal"); |
| - None |
| - } |
| - syn::NestedMeta::Lit(l) => Some(l), |
| - } |
| - } |
| - |
| expect_lit_fn![ |
| (expect_lit_str, LitStr, Str, "string"), |
| (expect_lit_char, LitChar, Char, "character"), |
| @@ -99,7 +77,7 @@ impl Errors { |
| (expect_meta_name_value, MetaNameValue, NameValue, "name-value pair"), |
| ]; |
| |
| - fn unexpected_lit(&self, expected: &str, found: &syn::Lit) { |
| + fn unexpected_lit(&self, expected: &str, found: &syn::Expr) { |
| fn lit_kind(lit: &syn::Lit) -> &'static str { |
| use syn::Lit::{Bool, Byte, ByteStr, Char, Float, Int, Str, Verbatim}; |
| match lit { |
| @@ -111,13 +89,21 @@ impl Errors { |
| Float(_) => "float", |
| Bool(_) => "boolean", |
| Verbatim(_) => "unknown (possibly extra-large integer)", |
| + _ => "unknown literal kind", |
| } |
| } |
| |
| - self.err( |
| - found, |
| - &["Expected ", expected, " literal, found ", lit_kind(found), " literal"].concat(), |
| - ) |
| + if let syn::Expr::Lit(syn::ExprLit { lit, .. }) = found { |
| + self.err( |
| + found, |
| + &["Expected ", expected, " literal, found ", lit_kind(lit), " literal"].concat(), |
| + ) |
| + } else { |
| + self.err( |
| + found, |
| + &["Expected ", expected, " literal, found non-literal expression."].concat(), |
| + ) |
| + } |
| } |
| |
| fn unexpected_meta(&self, expected: &str, found: &syn::Meta) { |
| @@ -155,6 +141,17 @@ impl Errors { |
| pub fn push(&self, err: syn::Error) { |
| self.errors.borrow_mut().push(err); |
| } |
| + |
| + /// Convert a `syn::Result` to an `Option`, logging the error if present. |
| + pub fn ok<T>(&self, r: syn::Result<T>) -> Option<T> { |
| + match r { |
| + Ok(v) => Some(v), |
| + Err(e) => { |
| + self.push(e); |
| + None |
| + } |
| + } |
| + } |
| } |
| |
| impl ToTokens for Errors { |
| diff --git a/src/parse_attrs.rs b/src/parse_attrs.rs |
| index 04dcbdd..10ebcb5 100644 |
| --- a/src/parse_attrs.rs |
| +++ b/src/parse_attrs.rs |
| @@ -76,49 +76,47 @@ impl FieldAttrs { |
| continue; |
| }; |
| |
| - for meta in &ml.nested { |
| - let meta = if let Some(m) = errors.expect_nested_meta(meta) { m } else { continue }; |
| - |
| + for meta in ml { |
| let name = meta.path(); |
| if name.is_ident("arg_name") { |
| - if let Some(m) = errors.expect_meta_name_value(meta) { |
| + if let Some(m) = errors.expect_meta_name_value(&meta) { |
| this.parse_attr_arg_name(errors, m); |
| } |
| } else if name.is_ident("default") { |
| - if let Some(m) = errors.expect_meta_name_value(meta) { |
| + if let Some(m) = errors.expect_meta_name_value(&meta) { |
| this.parse_attr_default(errors, m); |
| } |
| } else if name.is_ident("description") { |
| - if let Some(m) = errors.expect_meta_name_value(meta) { |
| + if let Some(m) = errors.expect_meta_name_value(&meta) { |
| parse_attr_description(errors, m, &mut this.description); |
| } |
| } else if name.is_ident("from_str_fn") { |
| - if let Some(m) = errors.expect_meta_list(meta) { |
| + if let Some(m) = errors.expect_meta_list(&meta) { |
| this.parse_attr_from_str_fn(errors, m); |
| } |
| } else if name.is_ident("long") { |
| - if let Some(m) = errors.expect_meta_name_value(meta) { |
| + if let Some(m) = errors.expect_meta_name_value(&meta) { |
| this.parse_attr_long(errors, m); |
| } |
| } else if name.is_ident("option") { |
| - parse_attr_field_type(errors, meta, FieldKind::Option, &mut this.field_type); |
| + parse_attr_field_type(errors, &meta, FieldKind::Option, &mut this.field_type); |
| } else if name.is_ident("short") { |
| - if let Some(m) = errors.expect_meta_name_value(meta) { |
| + if let Some(m) = errors.expect_meta_name_value(&meta) { |
| this.parse_attr_short(errors, m); |
| } |
| } else if name.is_ident("subcommand") { |
| parse_attr_field_type( |
| errors, |
| - meta, |
| + &meta, |
| FieldKind::SubCommand, |
| &mut this.field_type, |
| ); |
| } else if name.is_ident("switch") { |
| - parse_attr_field_type(errors, meta, FieldKind::Switch, &mut this.field_type); |
| + parse_attr_field_type(errors, &meta, FieldKind::Switch, &mut this.field_type); |
| } else if name.is_ident("positional") { |
| parse_attr_field_type( |
| errors, |
| - meta, |
| + &meta, |
| FieldKind::Positional, |
| &mut this.field_type, |
| ); |
| @@ -189,7 +187,7 @@ impl FieldAttrs { |
| fn parse_attr_short(&mut self, errors: &Errors, m: &syn::MetaNameValue) { |
| if let Some(first) = &self.short { |
| errors.duplicate_attrs("short", first, m); |
| - } else if let Some(lit_char) = errors.expect_lit_char(&m.lit) { |
| + } else if let Some(lit_char) = errors.expect_lit_char(&m.value) { |
| self.short = Some(lit_char.clone()); |
| if !lit_char.value().is_ascii() { |
| errors.err(lit_char, "Short names must be ASCII"); |
| @@ -217,16 +215,7 @@ fn parse_attr_fn_name( |
| errors.duplicate_attrs(attr_name, first, m); |
| } |
| |
| - if m.nested.len() != 1 { |
| - errors.err(&m.nested, "Expected a single argument containing the function name"); |
| - return; |
| - } |
| - |
| - // `unwrap` will not fail because of the call above |
| - let nested = m.nested.first().unwrap(); |
| - if let Some(path) = errors.expect_nested_meta(nested).and_then(|m| errors.expect_meta_word(m)) { |
| - *slot = path.get_ident().cloned(); |
| - } |
| + *slot = errors.ok(m.parse_args()); |
| } |
| |
| fn parse_attr_field_type( |
| @@ -246,7 +235,7 @@ fn parse_attr_field_type( |
| |
| // Whether the attribute is one like `#[<name> ...]` |
| fn is_matching_attr(name: &str, attr: &syn::Attribute) -> bool { |
| - attr.path.segments.len() == 1 && attr.path.segments[0].ident == name |
| + attr.path().segments.len() == 1 && attr.path().segments[0].ident == name |
| } |
| |
| /// Checks for `#[doc ...]`, which is generated by doc comments. |
| @@ -259,34 +248,18 @@ fn is_argh_attr(attr: &syn::Attribute) -> bool { |
| is_matching_attr("argh", attr) |
| } |
| |
| -fn attr_to_meta_subtype<R: Clone>( |
| +/// Filters out non-`#[argh(...)]` attributes and converts to a sequence of `syn::Meta`. |
| +fn argh_attr_to_meta_list( |
| errors: &Errors, |
| attr: &syn::Attribute, |
| - f: impl FnOnce(&syn::Meta) -> Option<&R>, |
| -) -> Option<R> { |
| - match attr.parse_meta() { |
| - Ok(meta) => f(&meta).cloned(), |
| - Err(e) => { |
| - errors.push(e); |
| - None |
| - } |
| - } |
| -} |
| - |
| -fn attr_to_meta_list(errors: &Errors, attr: &syn::Attribute) -> Option<syn::MetaList> { |
| - attr_to_meta_subtype(errors, attr, |m| errors.expect_meta_list(m)) |
| -} |
| - |
| -fn attr_to_meta_name_value(errors: &Errors, attr: &syn::Attribute) -> Option<syn::MetaNameValue> { |
| - attr_to_meta_subtype(errors, attr, |m| errors.expect_meta_name_value(m)) |
| -} |
| - |
| -/// Filters out non-`#[argh(...)]` attributes and converts to `syn::MetaList`. |
| -fn argh_attr_to_meta_list(errors: &Errors, attr: &syn::Attribute) -> Option<syn::MetaList> { |
| +) -> Option<impl IntoIterator<Item = syn::Meta>> { |
| if !is_argh_attr(attr) { |
| return None; |
| } |
| - attr_to_meta_list(errors, attr) |
| + let ml = errors.expect_meta_list(&attr.meta)?; |
| + errors.ok(ml.parse_args_with( |
| + syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated, |
| + )) |
| } |
| |
| /// Represents a `#[derive(FromArgs)]` type's top-level attributes. |
| @@ -317,32 +290,31 @@ impl TypeAttrs { |
| continue; |
| }; |
| |
| - for meta in &ml.nested { |
| - let meta = if let Some(m) = errors.expect_nested_meta(meta) { m } else { continue }; |
| - |
| + for meta in ml { |
| let name = meta.path(); |
| if name.is_ident("description") { |
| - if let Some(m) = errors.expect_meta_name_value(meta) { |
| + if let Some(m) = errors.expect_meta_name_value(&meta) { |
| parse_attr_description(errors, m, &mut this.description); |
| } |
| } else if name.is_ident("error_code") { |
| - if let Some(m) = errors.expect_meta_list(meta) { |
| + if let Some(m) = errors.expect_meta_list(&meta) { |
| this.parse_attr_error_code(errors, m); |
| } |
| } else if name.is_ident("example") { |
| - if let Some(m) = errors.expect_meta_name_value(meta) { |
| + if let Some(m) = errors.expect_meta_name_value(&meta) { |
| this.parse_attr_example(errors, m); |
| } |
| } else if name.is_ident("name") { |
| - if let Some(m) = errors.expect_meta_name_value(meta) { |
| + if let Some(m) = errors.expect_meta_name_value(&meta) { |
| this.parse_attr_name(errors, m); |
| } |
| } else if name.is_ident("note") { |
| - if let Some(m) = errors.expect_meta_name_value(meta) { |
| + if let Some(m) = errors.expect_meta_name_value(&meta) { |
| this.parse_attr_note(errors, m); |
| } |
| } else if name.is_ident("subcommand") { |
| - if let Some(ident) = errors.expect_meta_word(meta).and_then(|p| p.get_ident()) { |
| + if let Some(ident) = errors.expect_meta_word(&meta).and_then(|p| p.get_ident()) |
| + { |
| this.parse_attr_subcommand(errors, ident); |
| } |
| } else { |
| @@ -396,20 +368,17 @@ impl TypeAttrs { |
| } |
| |
| fn parse_attr_error_code(&mut self, errors: &Errors, ml: &syn::MetaList) { |
| - if ml.nested.len() != 2 { |
| - errors.err(&ml, "Expected two arguments, an error number and a string description"); |
| - return; |
| - } |
| - |
| - let err_code = &ml.nested[0]; |
| - let err_msg = &ml.nested[1]; |
| - |
| - let err_code = errors.expect_nested_lit(err_code).and_then(|l| errors.expect_lit_int(l)); |
| - let err_msg = errors.expect_nested_lit(err_msg).and_then(|l| errors.expect_lit_str(l)); |
| - |
| - if let (Some(err_code), Some(err_msg)) = (err_code, err_msg) { |
| - self.error_codes.push((err_code.clone(), err_msg.clone())); |
| - } |
| + errors.ok(ml.parse_args_with(|input: syn::parse::ParseStream| { |
| + let err_code = input.parse()?; |
| + input.parse::<syn::Token![,]>()?; |
| + let err_msg = input.parse()?; |
| + if let (Some(err_code), Some(err_msg)) = |
| + (errors.expect_lit_int(&err_code), errors.expect_lit_str(&err_msg)) |
| + { |
| + self.error_codes.push((err_code.clone(), err_msg.clone())); |
| + } |
| + Ok(()) |
| + })); |
| } |
| |
| fn parse_attr_example(&mut self, errors: &Errors, m: &syn::MetaNameValue) { |
| @@ -470,15 +439,13 @@ impl VariantAttrs { |
| continue; |
| }; |
| |
| - for meta in &ml.nested { |
| - let meta = if let Some(m) = errors.expect_nested_meta(meta) { m } else { continue }; |
| - |
| + for meta in ml { |
| let name = meta.path(); |
| if name.is_ident("dynamic") { |
| if let Some(prev) = this.is_dynamic.as_ref() { |
| - errors.duplicate_attrs("dynamic", prev, meta); |
| + errors.duplicate_attrs("dynamic", prev, &meta); |
| } else { |
| - this.is_dynamic = errors.expect_meta_word(meta).cloned(); |
| + this.is_dynamic = errors.expect_meta_word(&meta).cloned(); |
| } |
| } else { |
| errors.err( |
| @@ -515,19 +482,19 @@ fn parse_attr_single_string( |
| ) { |
| if let Some(first) = slot { |
| errors.duplicate_attrs(name, first, m); |
| - } else if let Some(lit_str) = errors.expect_lit_str(&m.lit) { |
| + } else if let Some(lit_str) = errors.expect_lit_str(&m.value) { |
| *slot = Some(lit_str.clone()); |
| } |
| } |
| |
| fn parse_attr_multi_string(errors: &Errors, m: &syn::MetaNameValue, list: &mut Vec<syn::LitStr>) { |
| - if let Some(lit_str) = errors.expect_lit_str(&m.lit) { |
| + if let Some(lit_str) = errors.expect_lit_str(&m.value) { |
| list.push(lit_str.clone()); |
| } |
| } |
| |
| fn parse_attr_doc(errors: &Errors, attr: &syn::Attribute, slot: &mut Option<Description>) { |
| - let nv = if let Some(nv) = attr_to_meta_name_value(errors, attr) { |
| + let nv = if let Some(nv) = errors.expect_meta_name_value(&attr.meta) { |
| nv |
| } else { |
| return; |
| @@ -538,7 +505,7 @@ fn parse_attr_doc(errors: &Errors, attr: &syn::Attribute, slot: &mut Option<Desc |
| return; |
| } |
| |
| - if let Some(lit_str) = errors.expect_lit_str(&nv.lit) { |
| + if let Some(lit_str) = errors.expect_lit_str(&nv.value) { |
| let lit_str = if let Some(previous) = slot { |
| let previous = &previous.content; |
| let previous_span = previous.span(); |
| @@ -551,7 +518,8 @@ fn parse_attr_doc(errors: &Errors, attr: &syn::Attribute, slot: &mut Option<Desc |
| } |
| |
| fn parse_attr_description(errors: &Errors, m: &syn::MetaNameValue, slot: &mut Option<Description>) { |
| - let lit_str = if let Some(lit_str) = errors.expect_lit_str(&m.lit) { lit_str } else { return }; |
| + let lit_str = |
| + if let Some(lit_str) = errors.expect_lit_str(&m.value) { lit_str } else { return }; |
| |
| // Don't allow multiple explicit (non doc-comment) descriptions |
| if let Some(description) = slot { |