Repository: dtolnay/syn (rust)

Issue:
ItemFn parse regression from 1.0.27 
I noted a regression introduced with 1.0.27 release on parsing ItemFn with attributes. Of course, this occurs  just in some rare cases but I found a simple example. I've a funktion-like procedural macro that merge the function attributes `merge_attr`. 

If I call it by:

```rust
merge_attrs! {
    #[test]
    fn template() {},
    #[should_panic]
    fn merge_ok() {assert!(false)}
}
```
works ok. 

But if I wrap it by macro like follow:

```rust
macro_rules! merge_some {
            ( $test:item ) => {
                        merge_attrs! {
                            #[test]
                            fn template() {},
                            $test
                        }
                    }
        }

merge_some! {
    #[should_panic]
    fn merge_fail() {assert!(false)}
}
```
`merge_attrs` fail on parse the second functions and tell that expect a `fn`... but works if I remove the attributes from this function.

This behavior is raised up from 1.0.27 version. (I see two commit that looks like very close to this issue).

Anyway if dump the token streams in both cases I can see that in the macro one case we have a
```
Group {
        delimiter: None,
        stream: TokenStream [
....
```
that wrap the function. 

In 1.0.27 a new check ` !input.peek(token::Group)` was introduced but I guess that we should consider the _void_ `Group` as valid.

`merge_attr` code:

```rust
struct MergeAttrs {
    template: ItemFn,
    function: ItemFn,
}

impl Parse for MergeAttrs {
    fn parse(input: parse::ParseStream) -> syn::Result<Self> {
        let template = input.parse()?;
        let _comma: Token![,] = input.parse()?;
        let function = input.parse()?;
        Ok(Self { template, function })
    }
}

#[proc_macro]
pub fn merge_attrs(item: TokenStream) -> TokenStream {
    let mut merge = parse_macro_input!(item as MergeAttrs);
    let mut attrs = merge.template.attrs.clone();
    attrs.append(&mut merge.function.attrs);
    merge.function.attrs = attrs;

    let f = merge.function;

    let tokens = quote! {
        #f
    };
    tokens.into()
}
```

Pull request:
Move none-delimited attr behavior only to exprs
Fixes #852.

Patch:
diff --git a/src/attr.rs b/src/attr.rs
index b23aead4..2a4bced1 100644
--- a/src/attr.rs
+++ b/src/attr.rs
@@ -263,7 +263,7 @@ impl Attribute {
     #[cfg(feature = "parsing")]
     pub fn parse_outer(input: ParseStream) -> Result<Vec<Self>> {
         let mut attrs = Vec::new();
-        while input.peek(Token![#]) && !input.peek(token::Group) {
+        while input.peek(Token![#]) {
             attrs.push(input.call(parsing::single_parse_outer)?);
         }
         Ok(attrs)
@@ -276,7 +276,7 @@ impl Attribute {
     #[cfg(feature = "parsing")]
     pub fn parse_inner(input: ParseStream) -> Result<Vec<Self>> {
         let mut attrs = Vec::new();
-        while input.peek(Token![#]) && input.peek2(Token![!]) && !input.peek(token::Group) {
+        while input.peek(Token![#]) && input.peek2(Token![!]) {
             attrs.push(input.call(parsing::single_parse_inner)?);
         }
         Ok(attrs)
diff --git a/src/expr.rs b/src/expr.rs
index 2f73c206..ef0fcfe3 100644
--- a/src/expr.rs
+++ b/src/expr.rs
@@ -1543,6 +1543,30 @@ pub(crate) mod parsing {
         parse_expr(input, lhs, allow_struct, Precedence::Any)
     }
 
+    #[cfg(feature = "full")]
+    fn expr_attrs(input: ParseStream) -> Result<Vec<Attribute>> {
+        let mut attrs = Vec::new();
+        loop {
+            if input.peek(token::Group) {
+                let ahead = input.fork();
+                let group = crate::group::parse_group(&ahead)?;
+                if !group.content.peek(Token![#]) || group.content.peek2(Token![!]) {
+                    break;
+                }
+                let attr = group.content.call(attr::parsing::single_parse_outer)?;
+                if !group.content.is_empty() {
+                    break;
+                }
+                attrs.push(attr);
+            } else if input.peek(Token![#]) {
+                attrs.push(input.call(attr::parsing::single_parse_outer)?);
+            } else {
+                break;
+            }
+        }
+        Ok(attrs)
+    }
+
     // <UnOp> <trailer>
     // & <trailer>
     // &mut <trailer>
@@ -1550,7 +1574,7 @@ pub(crate) mod parsing {
     #[cfg(feature = "full")]
     fn unary_expr(input: ParseStream, allow_struct: AllowStruct) -> Result<Expr> {
         let begin = input.fork();
-        let attrs = input.call(Attribute::parse_outer)?;
+        let attrs = input.call(expr_attrs)?;
         if input.peek(Token![&]) {
             let and_token: Token![&] = input.parse()?;
             let raw: Option<raw> =


Test patch:
diff --git a/tests/test_item.rs b/tests/test_item.rs
new file mode 100644
index 00000000..74ac4bae
--- /dev/null
+++ b/tests/test_item.rs
@@ -0,0 +1,45 @@
+#[macro_use]
+mod macros;
+
+use proc_macro2::{Delimiter, Group, Ident, Span, TokenStream, TokenTree};
+use quote::quote;
+use std::iter::FromIterator;
+use syn::Item;
+
+#[test]
+fn test_macro_variable_attr() {
+    // mimics the token stream corresponding to `$attr fn f() {}`
+    let tokens = TokenStream::from_iter(vec![
+        TokenTree::Group(Group::new(Delimiter::None, quote! { #[test] })),
+        TokenTree::Ident(Ident::new("fn", Span::call_site())),
+        TokenTree::Ident(Ident::new("f", Span::call_site())),
+        TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::new())),
+        TokenTree::Group(Group::new(Delimiter::Brace, TokenStream::new())),
+    ]);
+
+    snapshot!(tokens as Item, @r###"
+    Item::Fn {
+        attrs: [
+            Attribute {
+                style: Outer,
+                path: Path {
+                    segments: [
+                        PathSegment {
+                            ident: "test",
+                            arguments: None,
+                        },
+                    ],
+                },
+                tokens: TokenStream(``),
+            },
+        ],
+        vis: Inherited,
+        sig: Signature {
+            ident: "f",
+            generics: Generics,
+            output: Default,
+        },
+        block: Block,
+    }
+    "###);
+}