Repository: dtolnay/syn
Pull request #1642 (2024-05-11 02:45:49)

Disallow range with upper bound in lhs of binop
Previously, syn incorrectly accepted `.. $e ..` as:

```rust
Expr::Range {
    limits: RangeLimits::HalfOpen,
    end: Some(Expr::Range {
        start: Some($e),
        limits: RangeLimits::HalfOpen,
    }),
}
```

and `$e .. $e ..` as:

```rust
Expr::Range {
    start: Some(Expr::Range {
        start: Some($e),
        limits: RangeLimits::HalfOpen,
        end: Some($e),
    }),
    limits: RangeLimits::HalfOpen,
}
```

but these are not legal expressions in Rust. A range with a lower bound cannot be the upper bound of another range, and a range with an upper bound cannot be the lower bound of another range.

```console
error: expected one of `.`, `;`, `?`, `else`, or an operator, found `..`
 --> src/main.rs:2:19
  |
2 |     let _ = .. () ..;
  |                   ^^ expected one of `.`, `;`, `?`, `else`, or an operator

error: expected one of `.`, `;`, `?`, `else`, or an operator, found `..`
 --> src/main.rs:3:22
  |
3 |     let _ = () .. () ..;
  |                      ^^ expected one of `.`, `;`, `?`, `else`, or an operator
```


Patch:
diff --git a/src/expr.rs b/src/expr.rs
index ca16fdee..4183ced2 100644
--- a/src/expr.rs
+++ b/src/expr.rs
@@ -1391,7 +1391,11 @@ pub(crate) mod parsing {
     ) -> Result<Expr> {
         loop {
             let ahead = input.fork();
-            if let Ok(op) = ahead.parse::<BinOp>() {
+            if let Expr::Range(ExprRange { end: Some(_), .. }) = lhs {
+                // A range with an upper bound cannot be the left-hand side of
+                // another binary operator.
+                break;
+            } else if let Ok(op) = ahead.parse::<BinOp>() {
                 let precedence = Precedence::of(&op);
                 if precedence < base {
                     break;


Test patch:
diff --git a/tests/test_expr.rs b/tests/test_expr.rs
index daf0d63c..13c37b6b 100644
--- a/tests/test_expr.rs
+++ b/tests/test_expr.rs
@@ -331,7 +331,7 @@ fn test_postfix_operator_after_cast() {
 }
 
 #[test]
-fn test_ranges() {
+fn test_range_kinds() {
     syn::parse_str::<Expr>("..").unwrap();
     syn::parse_str::<Expr>("..hi").unwrap();
     syn::parse_str::<Expr>("lo..").unwrap();
@@ -348,6 +348,44 @@ fn test_ranges() {
     syn::parse_str::<Expr>("lo...hi").unwrap_err();
 }
 
+#[test]
+fn test_range_precedence() {
+    snapshot!(".. .." as Expr, @r###"
+    Expr::Range {
+        limits: RangeLimits::HalfOpen,
+        end: Some(Expr::Range {
+            limits: RangeLimits::HalfOpen,
+        }),
+    }
+    "###);
+
+    snapshot!(".. .. ()" as Expr, @r###"
+    Expr::Range {
+        limits: RangeLimits::HalfOpen,
+        end: Some(Expr::Range {
+            limits: RangeLimits::HalfOpen,
+            end: Some(Expr::Tuple),
+        }),
+    }
+    "###);
+
+    snapshot!("() .. .." as Expr, @r###"
+    Expr::Range {
+        start: Some(Expr::Tuple),
+        limits: RangeLimits::HalfOpen,
+        end: Some(Expr::Range {
+            limits: RangeLimits::HalfOpen,
+        }),
+    }
+    "###);
+
+    // A range with a lower bound cannot be the upper bound of another range,
+    // and a range with an upper bound cannot be the lower bound of another
+    // range.
+    syn::parse_str::<Expr>(".. x ..").unwrap_err();
+    syn::parse_str::<Expr>("x .. x ..").unwrap_err();
+}
+
 #[test]
 fn test_ambiguous_label() {
     for stmt in [