Repository: dtolnay/syn
Pull request #1544 (2023-12-11 02:20:56)

Fix stmt boundary after None-delimited group containing loop
Previously `match v { _ => $expr }` with $expr=`loop {} + 1` would fail to parse, because the arm would get terminated after the `loop {}` and then `+ 1` would try to get parsed as a pattern.


Patch:
diff --git a/src/expr.rs b/src/expr.rs
index 334caada..57b38150 100644
--- a/src/expr.rs
+++ b/src/expr.rs
@@ -1915,7 +1915,15 @@ pub(crate) mod parsing {
     #[cfg(feature = "full")]
     pub(crate) fn expr_early(input: ParseStream) -> Result<Expr> {
         let mut attrs = input.call(expr_attrs)?;
-        let mut expr = if input.peek(Token![if]) {
+        let mut expr = if input.peek(token::Group) {
+            let allow_struct = AllowStruct(true);
+            let atom = expr_group(input, allow_struct)?;
+            if continue_parsing_early(&atom) {
+                trailer_helper(input, atom)?
+            } else {
+                atom
+            }
+        } else if input.peek(Token![if]) {
             Expr::If(input.parse()?)
         } else if input.peek(Token![while]) {
             Expr::While(input.parse()?)
@@ -1939,13 +1947,16 @@ pub(crate) mod parsing {
             atom_labeled(input)?
         } else {
             let allow_struct = AllowStruct(true);
-            let mut expr = unary_expr(input, allow_struct)?;
+            unary_expr(input, allow_struct)?
+        };
 
+        if continue_parsing_early(&expr) {
             attrs.extend(expr.replace_attrs(Vec::new()));
             expr.replace_attrs(attrs);
 
+            let allow_struct = AllowStruct(true);
             return parse_expr(input, expr, allow_struct, Precedence::Any);
-        };
+        }
 
         if input.peek(Token![.]) && !input.peek(Token![..]) || input.peek(Token![?]) {
             expr = trailer_helper(input, expr)?;
@@ -1962,6 +1973,25 @@ pub(crate) mod parsing {
         Ok(expr)
     }
 
+    #[cfg(feature = "full")]
+    fn continue_parsing_early(mut expr: &Expr) -> bool {
+        while let Expr::Group(group) = expr {
+            expr = &group.expr;
+        }
+        match expr {
+            Expr::If(_)
+            | Expr::While(_)
+            | Expr::ForLoop(_)
+            | Expr::Loop(_)
+            | Expr::Match(_)
+            | Expr::TryBlock(_)
+            | Expr::Unsafe(_)
+            | Expr::Const(_)
+            | Expr::Block(_) => false,
+            _ => true,
+        }
+    }
+
     #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
     impl Parse for ExprLit {
         fn parse(input: ParseStream) -> Result<Self> {


Test patch:
diff --git a/tests/test_expr.rs b/tests/test_expr.rs
index cd3098ab..449b83bf 100644
--- a/tests/test_expr.rs
+++ b/tests/test_expr.rs
@@ -235,7 +235,6 @@ fn test_macro_variable_match_arm() {
     // mimics the token stream corresponding to `match v { _ => $expr }`
     let expr = Group::new(Delimiter::None, quote! { #[a] () });
     let tokens = quote!(match v { _ => #expr });
-
     snapshot!(tokens as Expr, @r###"
     Expr::Match {
         expr: Expr::Path {
@@ -270,6 +269,40 @@ fn test_macro_variable_match_arm() {
         ],
     }
     "###);
+
+    let expr = Group::new(Delimiter::None, quote!(loop {} + 1));
+    let tokens = quote!(match v { _ => #expr });
+    snapshot!(tokens as Expr, @r###"
+    Expr::Match {
+        expr: Expr::Path {
+            path: Path {
+                segments: [
+                    PathSegment {
+                        ident: "v",
+                    },
+                ],
+            },
+        },
+        arms: [
+            Arm {
+                pat: Pat::Wild,
+                body: Expr::Group {
+                    expr: Expr::Binary {
+                        left: Expr::Loop {
+                            body: Block {
+                                stmts: [],
+                            },
+                        },
+                        op: BinOp::Add,
+                        right: Expr::Lit {
+                            lit: 1,
+                        },
+                    },
+                },
+            },
+        ],
+    }
+    "###);
 }
 
 // https://github.com/dtolnay/syn/issues/1019