Repository: dtolnay/syn (rust)

Issue:
Parsing a `syn::ItemFn` fails with "unexpected token" on nightlies newer than 2020-05-24
Caused by rust-lang/rust#72388; [this](https://github.com/rustwasm/wasm-bindgen/pull/2159#issuecomment-633951247) has more details.

Here's a (kind of, but not really) minified test case:
```rust
macro_rules! import_macro {
    ($(($rust:ident, $js:ident, $i:ident))*) => ($(
        #[wasm_bindgen]
        pub fn $rust(a: &[$i]) -> Vec<$i> {
            $js(a, Some(a), None)
        }
    )*)
}

import_macro! {
    (import_rust_i8, import_js_i8, i8)
}
```

`#[wasm_bindgen]` tries to parse the function as a `syn::ItemFn` which fails on the function's block (specifically on the `(` after `$js`). As mentioned in the linked issue, as best as I can tell, this happens because `trailer_expr` (in `expr.rs`) returns if it sees a group which causes `Block::parse_within` to only see `$js` rather than the whole expression. `Block::parse_within` then expects a semicolon after `$js` (since it's not the last thing in the block) which it doesn't see and so it errors.

As mentioned in the linked issue's thread, removing the `token::Group` check in `trailer_expr` seems to fix this problem but I don't know if this will cause other problems; I'm not very familiar with `syn`'s internals.

I noticed #832 altered that exact check; I tested again with 1.0.26 and I still get the same error.

Also, slightly unrelated, but would it be correct for the error [here](https://github.com/dtolnay/syn/blob/243142aed174856041a4cd61f449d4e09c80e8ff/src/stmt.rs#L126) to be "expected semicolon"?

Pull request description:
Parse trailer exprs where the lhs is a Group
Fixes #833.

Patch:
diff --git a/src/attr.rs b/src/attr.rs
index 78c84e14..2a51c380 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![#]) {
+        while input.peek(Token![#]) && !input.peek(token::Group) {
             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![!]) {
+        while input.peek(Token![#]) && input.peek2(Token![!]) && !input.peek(token::Group) {
             attrs.push(input.call(parsing::single_parse_inner)?);
         }
         Ok(attrs)
diff --git a/src/expr.rs b/src/expr.rs
index 77e5568d..ce87136e 100644
--- a/src/expr.rs
+++ b/src/expr.rs
@@ -1545,10 +1545,6 @@ pub(crate) mod parsing {
     // <atom> ? ...
     #[cfg(feature = "full")]
     fn trailer_expr(input: ParseStream, allow_struct: AllowStruct) -> Result<Expr> {
-        if input.peek(token::Group) && !input.peek2(Token![::]) {
-            return input.call(expr_group).map(Expr::Group);
-        }
-
         let outer_attrs = input.call(Attribute::parse_outer)?;
 
         let atom = atom_expr(input, allow_struct)?;


Test patch:
diff --git a/tests/test_expr.rs b/tests/test_expr.rs
index 0edf6cee..153c4d02 100644
--- a/tests/test_expr.rs
+++ b/tests/test_expr.rs
@@ -3,7 +3,9 @@ mod macros;
 
 use std::str::FromStr;
 
-use proc_macro2::TokenStream;
+use proc_macro2::{Delimiter, Group, Punct, Spacing, TokenStream, TokenTree};
+use quote::quote;
+use std::iter::FromIterator;
 use syn::{Expr, ExprRange};
 
 #[test]
@@ -35,3 +37,81 @@ fn test_await() {
     }
     "###);
 }
+
+#[test]
+fn test_macro_variable_func() {
+    // mimics the token stream corresponding to `$fn()`
+    let tokens = TokenStream::from_iter(vec![
+        TokenTree::Group(Group::new(Delimiter::None, quote! { f })),
+        TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::new())),
+    ]);
+
+    snapshot!(tokens as Expr, @r###"
+    Expr::Call {
+        func: Expr::Group {
+            expr: Expr::Path {
+                path: Path {
+                    segments: [
+                        PathSegment {
+                            ident: "f",
+                            arguments: None,
+                        },
+                    ],
+                },
+            },
+        },
+    }
+    "###);
+
+    let tokens = TokenStream::from_iter(vec![
+        TokenTree::Punct(Punct::new('#', Spacing::Alone)),
+        TokenTree::Group(Group::new(Delimiter::Bracket, quote! { outside })),
+        TokenTree::Group(Group::new(Delimiter::None, quote! { #[inside] f })),
+        TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::new())),
+    ]);
+
+    snapshot!(tokens as Expr, @r###"
+    Expr::Call {
+        attrs: [
+            Attribute {
+                style: Outer,
+                path: Path {
+                    segments: [
+                        PathSegment {
+                            ident: "outside",
+                            arguments: None,
+                        },
+                    ],
+                },
+                tokens: ``,
+            },
+        ],
+        func: Expr::Group {
+            expr: Expr::Path {
+                attrs: [
+                    Attribute {
+                        style: Outer,
+                        path: Path {
+                            segments: [
+                                PathSegment {
+                                    ident: "inside",
+                                    arguments: None,
+                                },
+                            ],
+                        },
+                        tokens: ``,
+                    },
+                ],
+                path: Path {
+                    segments: [
+                        PathSegment {
+                            ident: "f",
+                            arguments: None,
+                        },
+                    ],
+                },
+            },
+        },
+    }
+    "###);
+}