Repository: dtolnay/syn
Pull request #1643 (2024-05-11 03:27:46)

Require parens for chained comparison binops
Previously, syn incorrectly accepted `$a == $b == $c` as an expression:

```rust
Expr::Binary {
    left: Expr::Binary {
        left: $a,
        op: BinOp::Eq,
        right: $b,
    },
    op: BinOp::Eq,
    right: $c,
}
```

but this is not a legal expression in Rust. The expression either needs to be parenthesized (`($a == $b) == $c` or `$a == ($b == $c)`) or turned into a conjunction (`$a == $b && $b == $c`).

```console
error: comparison operators cannot be chained
 --> src/main.rs:2:18
  |
2 |     let _ = true == true == true;
  |                  ^^      ^^
  |
help: split the comparison into two
  |
2 |     let _ = true == true && true == true;
  |                          +++++++
```


Patch:
diff --git a/src/expr.rs b/src/expr.rs
index 4183ced2..8b44342e 100644
--- a/src/expr.rs
+++ b/src/expr.rs
@@ -1400,6 +1400,13 @@ pub(crate) mod parsing {
                 if precedence < base {
                     break;
                 }
+                if precedence == Precedence::Compare {
+                    if let Expr::Binary(lhs) = &lhs {
+                        if Precedence::of(&lhs.op) == Precedence::Compare {
+                            break;
+                        }
+                    }
+                }
                 input.advance_to(&ahead);
                 let right = parse_binop_rhs(input, allow_struct, precedence)?;
                 lhs = Expr::Binary(ExprBinary {
@@ -1455,6 +1462,13 @@ pub(crate) mod parsing {
                 if precedence < base {
                     break;
                 }
+                if precedence == Precedence::Compare {
+                    if let Expr::Binary(lhs) = &lhs {
+                        if Precedence::of(&lhs.op) == Precedence::Compare {
+                            break;
+                        }
+                    }
+                }
                 input.advance_to(&ahead);
                 let right = parse_binop_rhs(input, precedence)?;
                 lhs = Expr::Binary(ExprBinary {


Test patch:
diff --git a/tests/test_expr.rs b/tests/test_expr.rs
index b908311d..f39200d3 100644
--- a/tests/test_expr.rs
+++ b/tests/test_expr.rs
@@ -577,6 +577,38 @@ fn test_tuple_comma() {
     "###);
 }
 
+#[test]
+fn test_binop_associativity() {
+    // Left to right.
+    snapshot!("() + () + ()" as Expr, @r###"
+    Expr::Binary {
+        left: Expr::Binary {
+            left: Expr::Tuple,
+            op: BinOp::Add,
+            right: Expr::Tuple,
+        },
+        op: BinOp::Add,
+        right: Expr::Tuple,
+    }
+    "###);
+
+    // Right to left.
+    snapshot!("() += () += ()" as Expr, @r###"
+    Expr::Binary {
+        left: Expr::Tuple,
+        op: BinOp::AddAssign,
+        right: Expr::Binary {
+            left: Expr::Tuple,
+            op: BinOp::AddAssign,
+            right: Expr::Tuple,
+        },
+    }
+    "###);
+
+    // Parenthesization is required.
+    syn::parse_str::<Expr>("() == () == ()").unwrap_err();
+}
+
 #[test]
 fn test_assign_range_precedence() {
     // Range has higher precedence as the right-hand of an assignment, but