Repository: dtolnay/syn
Pull request #1539 (2023-12-11 01:18:10)

Fix parsing of labeled loop followed by paren in stmt or match arm position
Previously, `loop {} ()` was correctly parsed as:

```rust
Stmt::Expr(
    Expr::Loop {
        body: Block,
    },
    None,
),
Stmt::Expr(
    Expr::Tuple,
    None,
),
```

but `'a: loop {} ()` was parsed incorrectly:

```rust
Stmt::Expr(
    Expr::Call {
        func: Expr::Loop {
            label: Some(Label {
                name: Lifetime {
                    ident: "a",
                },
            }),
            body: Block,
        },
    },
    None,
),
```

They both need to be parsed as `Expr::Loop` followed by `Expr::Tuple`.


Patch:
diff --git a/src/expr.rs b/src/expr.rs
index 22400be3..ca6471c1 100644
--- a/src/expr.rs
+++ b/src/expr.rs
@@ -1663,31 +1663,36 @@ pub(crate) mod parsing {
         } else if input.peek(Token![_]) {
             input.parse().map(Expr::Infer)
         } else if input.peek(Lifetime) {
-            let the_label: Label = input.parse()?;
-            let mut expr = if input.peek(Token![while]) {
-                Expr::While(input.parse()?)
-            } else if input.peek(Token![for]) {
-                Expr::ForLoop(input.parse()?)
-            } else if input.peek(Token![loop]) {
-                Expr::Loop(input.parse()?)
-            } else if input.peek(token::Brace) {
-                Expr::Block(input.parse()?)
-            } else {
-                return Err(input.error("expected loop or block expression"));
-            };
-            match &mut expr {
-                Expr::While(ExprWhile { label, .. })
-                | Expr::ForLoop(ExprForLoop { label, .. })
-                | Expr::Loop(ExprLoop { label, .. })
-                | Expr::Block(ExprBlock { label, .. }) => *label = Some(the_label),
-                _ => unreachable!(),
-            }
-            Ok(expr)
+            atom_labeled(input)
         } else {
             Err(input.error("expected an expression"))
         }
     }
 
+    #[cfg(feature = "full")]
+    fn atom_labeled(input: ParseStream) -> Result<Expr> {
+        let the_label: Label = input.parse()?;
+        let mut expr = if input.peek(Token![while]) {
+            Expr::While(input.parse()?)
+        } else if input.peek(Token![for]) {
+            Expr::ForLoop(input.parse()?)
+        } else if input.peek(Token![loop]) {
+            Expr::Loop(input.parse()?)
+        } else if input.peek(token::Brace) {
+            Expr::Block(input.parse()?)
+        } else {
+            return Err(input.error("expected loop or block expression"));
+        };
+        match &mut expr {
+            Expr::While(ExprWhile { label, .. })
+            | Expr::ForLoop(ExprForLoop { label, .. })
+            | Expr::Loop(ExprLoop { label, .. })
+            | Expr::Block(ExprBlock { label, .. }) => *label = Some(the_label),
+            _ => unreachable!(),
+        }
+        Ok(expr)
+    }
+
     #[cfg(not(feature = "full"))]
     fn atom_expr(input: ParseStream) -> Result<Expr> {
         if input.peek(token::Group)
@@ -1935,6 +1940,8 @@ pub(crate) mod parsing {
             Expr::Const(input.parse()?)
         } else if input.peek(token::Brace) {
             Expr::Block(input.parse()?)
+        } else if input.peek(Lifetime) {
+            atom_labeled(input)?
         } else {
             let allow_struct = AllowStruct(true);
             let mut expr = unary_expr(input, allow_struct)?;


Test patch:
diff --git a/tests/test_stmt.rs b/tests/test_stmt.rs
index bc57685d..b27ee581 100644
--- a/tests/test_stmt.rs
+++ b/tests/test_stmt.rs
@@ -9,7 +9,8 @@ mod macros;
 
 use proc_macro2::{Delimiter, Group, Ident, Span, TokenStream, TokenTree};
 use quote::quote;
-use syn::Stmt;
+use syn::parse::Parser as _;
+use syn::{Block, Stmt};
 
 #[test]
 fn test_raw_operator() {
@@ -234,3 +235,57 @@ fn test_macros() {
     })
     "###);
 }
+
+#[test]
+fn test_early_parse_loop() {
+    // The following is an Expr::Loop followed by Expr::Tuple. It is not an
+    // Expr::Call.
+    let tokens = quote! {
+        loop {}
+        ()
+    };
+
+    let stmts = Block::parse_within.parse2(tokens).unwrap();
+
+    snapshot!(stmts, @r###"
+    [
+        Stmt::Expr(
+            Expr::Loop {
+                body: Block,
+            },
+            None,
+        ),
+        Stmt::Expr(
+            Expr::Tuple,
+            None,
+        ),
+    ]
+    "###);
+
+    let tokens = quote! {
+        'a: loop {}
+        ()
+    };
+
+    let stmts = Block::parse_within.parse2(tokens).unwrap();
+
+    snapshot!(stmts, @r###"
+    [
+        Stmt::Expr(
+            Expr::Loop {
+                label: Some(Label {
+                    name: Lifetime {
+                        ident: "a",
+                    },
+                }),
+                body: Block,
+            },
+            None,
+        ),
+        Stmt::Expr(
+            Expr::Tuple,
+            None,
+        ),
+    ]
+    "###);
+}