Repository: shepmaster/snafu (rust)

Issue:
Multiple SNAFU attributes are not supported in one attribute
It is intended that we can say `#[snafu(a, b, c)]`, but it doesn't appear to work:

```rust
#[derive(Debug, Snafu)]
pub(crate) enum Error {
    #[snafu(visibility(pub(crate)), display("Moo"))]
    Alpha,
}
```

```
error: unexpected token
 --> tests/multi.rs:8:39
  |
8 |         #[snafu(visibility(pub(crate)), display("Moo"))]
  |                                       ^
```

Pull request description:
Fix parsing of multiple attributes inside `#[snafu(...)]`
Fixes #135

This was simple when using shepmaster's suggestion and test case from #135 - parse and remove the parentheses at the list level rather than the attribute level because the parentheses aren't part of the attributes themselves.

---

**Testing done:**

New test case shows multiple attributes being accepted and applied correctly.

Patch:
diff --git a/snafu-derive/src/lib.rs b/snafu-derive/src/lib.rs
index 5dd3af4..090f6f2 100644
--- a/snafu-derive/src/lib.rs
+++ b/snafu-derive/src/lib.rs
@@ -915,35 +915,32 @@ impl syn::parse::Parse for SnafuAttribute {
     fn parse(input: syn::parse::ParseStream) -> SynResult<Self> {
         use syn::{Expr, Ident, Visibility};
 
-        let inside;
-        parenthesized!(inside in input);
-        let name: Ident = inside.parse()?;
-
+        let name: Ident = input.parse()?;
         if name == "display" {
-            let m: MyMeta<List<Expr>> = inside.parse()?;
+            let m: MyMeta<List<Expr>> = input.parse()?;
             let v = m.into_option().ok_or_else(|| {
                 syn::Error::new(name.span(), "`snafu(display)` requires an argument")
             })?;
             let v = Box::new(v.0);
             Ok(SnafuAttribute::Display(v))
         } else if name == "visibility" {
-            let m: MyMeta<Visibility> = inside.parse()?;
+            let m: MyMeta<Visibility> = input.parse()?;
             let v = m
                 .into_option()
                 .map_or_else(private_visibility, |v| Box::new(v) as UserInput);
             Ok(SnafuAttribute::Visibility(v))
         } else if name == "source" {
-            if inside.is_empty() {
+            if input.is_empty() {
                 Ok(SnafuAttribute::Source(vec![Source::Flag(true)]))
             } else {
-                let v: MyParens<List<Source>> = inside.parse()?;
+                let v: MyParens<List<Source>> = input.parse()?;
                 Ok(SnafuAttribute::Source(v.0.into_vec()))
             }
         } else if name == "backtrace" {
-            if inside.is_empty() {
+            if input.is_empty() {
                 Ok(SnafuAttribute::Backtrace(Backtrace::Flag(true)))
             } else {
-                let v: MyParens<Backtrace> = inside.parse()?;
+                let v: MyParens<Backtrace> = input.parse()?;
                 Ok(SnafuAttribute::Backtrace(v.0))
             }
         } else {
@@ -962,8 +959,13 @@ impl syn::parse::Parse for SnafuAttributeBody {
         use syn::punctuated::Punctuated;
         use syn::token::Comma;
 
+        // Remove the parentheses from `#[snafu(...)]` so SnafuAttribute only has to deal with the
+        // tokens inside.
+        let inside;
+        parenthesized!(inside in input);
+
         let parse_comma_list = Punctuated::<SnafuAttribute, Comma>::parse_terminated;
-        let list = parse_comma_list(input)?;
+        let list = parse_comma_list(&inside)?;
 
         Ok(SnafuAttributeBody(
             list.into_pairs().map(|p| p.into_value()).collect(),


Test patch:
diff --git a/tests/multiple_attributes.rs b/tests/multiple_attributes.rs
new file mode 100644
index 0000000..000b273
--- /dev/null
+++ b/tests/multiple_attributes.rs
@@ -0,0 +1,25 @@
+extern crate snafu;
+
+mod error {
+    use snafu::Snafu;
+
+    #[derive(Debug, Snafu)]
+    pub(super) enum Error {
+        // We'll test both of these attributes inside `#[snafu(...)]`
+        #[snafu(visibility(pub(super)), display("Moo"))]
+        Alpha,
+    }
+}
+
+// Confirm `pub(super)` is applied to the generated struct
+#[test]
+fn is_visible() {
+    let _ = error::Alpha;
+}
+
+// Confirm `display("Moo")` is applied to the variant
+#[test]
+fn has_display() {
+    let err = error::Error::Alpha;
+    assert_eq!(format!("{}", err), "Moo");
+}