From 5af861cf7b4a87857ca6aa9dab2ad277ea8c059b Mon Sep 17 00:00:00 2001
From: Dominik Stolz <d.stolz@tum.de>
Date: Wed, 17 Apr 2024 23:40:03 +0200
Subject: [PATCH] Disallow ambiguous attributes on expressions

---
 compiler/rustc_parse/messages.ftl             |  2 +
 compiler/rustc_parse/src/errors.rs            |  9 ++++
 compiler/rustc_parse/src/parser/expr.rs       | 32 +++++++------
 .../clippy/tests/ui/cfg_attr_rustfmt.fixed    |  6 +--
 src/tools/clippy/tests/ui/cfg_attr_rustfmt.rs |  6 +--
 src/tools/rustfmt/tests/source/attrib.rs      |  4 +-
 src/tools/rustfmt/tests/target/attrib.rs      |  4 +-
 tests/pretty/ast-stmt-expr-attr.rs            | 18 ++++----
 tests/pretty/stmt_expr_attributes.rs          | 12 ++---
 .../unused/unused-doc-comments-edge-cases.rs  |  4 +-
 .../unused-doc-comments-edge-cases.stderr     | 14 +++---
 .../attribute/attr-binary-expr-ambigous.fixed | 15 ++++++
 .../attribute/attr-binary-expr-ambigous.rs    | 15 ++++++
 .../attr-binary-expr-ambigous.stderr          | 46 +++++++++++++++++++
 tests/ui/proc-macro/issue-81555.rs            |  3 +-
 15 files changed, 141 insertions(+), 49 deletions(-)
 create mode 100644 tests/ui/parser/attribute/attr-binary-expr-ambigous.fixed
 create mode 100644 tests/ui/parser/attribute/attr-binary-expr-ambigous.rs
 create mode 100644 tests/ui/parser/attribute/attr-binary-expr-ambigous.stderr

diff --git a/compiler/rustc_parse/messages.ftl b/compiler/rustc_parse/messages.ftl
index e2436759c22..9dcf5a57610 100644
--- a/compiler/rustc_parse/messages.ftl
+++ b/compiler/rustc_parse/messages.ftl
@@ -620,6 +620,8 @@ parse_or_pattern_not_allowed_in_let_binding = top-level or-patterns are not allo
 parse_out_of_range_hex_escape = out of range hex escape
     .label = must be a character in the range [\x00-\x7f]
 
+parse_outer_attr_ambiguous = ambiguous outer attributes
+
 parse_outer_attr_explanation = outer attributes, like `#[test]`, annotate the item following them
 
 parse_outer_attribute_not_allowed_on_if_else = outer attributes are not allowed on `if` and `else` branches
diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs
index eae2d904c35..d8cac23f2f1 100644
--- a/compiler/rustc_parse/src/errors.rs
+++ b/compiler/rustc_parse/src/errors.rs
@@ -495,6 +495,15 @@ pub(crate) struct OuterAttributeNotAllowedOnIfElse {
     pub attributes: Span,
 }
 
+#[derive(Diagnostic)]
+#[diag(parse_outer_attr_ambiguous)]
+pub(crate) struct AmbiguousOuterAttributes {
+    #[primary_span]
+    pub span: Span,
+    #[subdiagnostic]
+    pub sugg: WrapInParentheses,
+}
+
 #[derive(Diagnostic)]
 #[diag(parse_missing_in_in_for_loop)]
 pub(crate) struct MissingInInForLoop {
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index 00947a4c585..1625173970a 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -327,7 +327,9 @@ impl<'a> Parser<'a> {
                 this.parse_expr_assoc_with(prec + prec_adjustment, LhsExpr::NotYetParsed)
             })?;
 
-            let span = self.mk_expr_sp(&lhs, lhs_span, rhs.span);
+            self.error_ambiguous_outer_attrs(&lhs, lhs_span, rhs.span);
+            let span = lhs_span.to(rhs.span);
+
             lhs = match op {
                 AssocOp::Add
                 | AssocOp::Subtract
@@ -426,6 +428,18 @@ impl<'a> Parser<'a> {
         });
     }
 
+    fn error_ambiguous_outer_attrs(&self, lhs: &P<Expr>, lhs_span: Span, rhs_span: Span) {
+        if let Some(attr) = lhs.attrs.iter().find(|a| a.style == AttrStyle::Outer) {
+            self.dcx().emit_err(errors::AmbiguousOuterAttributes {
+                span: attr.span.to(rhs_span),
+                sugg: errors::WrapInParentheses::Expression {
+                    left: attr.span.shrink_to_lo(),
+                    right: lhs_span.shrink_to_hi(),
+                },
+            });
+        }
+    }
+
     /// Possibly translate the current token to an associative operator.
     /// The method does not advance the current token.
     ///
@@ -506,7 +520,8 @@ impl<'a> Parser<'a> {
             None
         };
         let rhs_span = rhs.as_ref().map_or(cur_op_span, |x| x.span);
-        let span = self.mk_expr_sp(&lhs, lhs.span, rhs_span);
+        self.error_ambiguous_outer_attrs(&lhs, lhs.span, rhs_span);
+        let span = lhs.span.to(rhs_span);
         let limits =
             if op == AssocOp::DotDot { RangeLimits::HalfOpen } else { RangeLimits::Closed };
         let range = self.mk_range(Some(lhs), rhs, limits);
@@ -722,7 +737,8 @@ impl<'a> Parser<'a> {
         expr_kind: fn(P<Expr>, P<Ty>) -> ExprKind,
     ) -> PResult<'a, P<Expr>> {
         let mk_expr = |this: &mut Self, lhs: P<Expr>, rhs: P<Ty>| {
-            this.mk_expr(this.mk_expr_sp(&lhs, lhs_span, rhs.span), expr_kind(lhs, rhs))
+            this.error_ambiguous_outer_attrs(&lhs, lhs_span, rhs.span);
+            this.mk_expr(lhs_span.to(rhs.span), expr_kind(lhs, rhs))
         };
 
         // Save the state of the parser before parsing type normally, in case there is a
@@ -3807,16 +3823,6 @@ impl<'a> Parser<'a> {
         self.mk_expr(span, ExprKind::Err(guar))
     }
 
-    /// Create expression span ensuring the span of the parent node
-    /// is larger than the span of lhs and rhs, including the attributes.
-    fn mk_expr_sp(&self, lhs: &P<Expr>, lhs_span: Span, rhs_span: Span) -> Span {
-        lhs.attrs
-            .iter()
-            .find(|a| a.style == AttrStyle::Outer)
-            .map_or(lhs_span, |a| a.span)
-            .to(rhs_span)
-    }
-
     fn collect_tokens_for_expr(
         &mut self,
         attrs: AttrWrapper,
diff --git a/src/tools/clippy/tests/ui/cfg_attr_rustfmt.fixed b/src/tools/clippy/tests/ui/cfg_attr_rustfmt.fixed
index 05d5b3d10ea..84dac431169 100644
--- a/src/tools/clippy/tests/ui/cfg_attr_rustfmt.fixed
+++ b/src/tools/clippy/tests/ui/cfg_attr_rustfmt.fixed
@@ -16,7 +16,7 @@ fn foo(
 
 fn skip_on_statements() {
     #[rustfmt::skip]
-    5+3;
+    { 5+3; }
 }
 
 #[rustfmt::skip]
@@ -33,11 +33,11 @@ mod foo {
 #[clippy::msrv = "1.29"]
 fn msrv_1_29() {
     #[cfg_attr(rustfmt, rustfmt::skip)]
-    1+29;
+    { 1+29; }
 }
 
 #[clippy::msrv = "1.30"]
 fn msrv_1_30() {
     #[rustfmt::skip]
-    1+30;
+    { 1+30; }
 }
diff --git a/src/tools/clippy/tests/ui/cfg_attr_rustfmt.rs b/src/tools/clippy/tests/ui/cfg_attr_rustfmt.rs
index bc29e20210e..4ab5c70e13b 100644
--- a/src/tools/clippy/tests/ui/cfg_attr_rustfmt.rs
+++ b/src/tools/clippy/tests/ui/cfg_attr_rustfmt.rs
@@ -16,7 +16,7 @@ fn foo(
 
 fn skip_on_statements() {
     #[cfg_attr(rustfmt, rustfmt::skip)]
-    5+3;
+    { 5+3; }
 }
 
 #[cfg_attr(rustfmt, rustfmt_skip)]
@@ -33,11 +33,11 @@ mod foo {
 #[clippy::msrv = "1.29"]
 fn msrv_1_29() {
     #[cfg_attr(rustfmt, rustfmt::skip)]
-    1+29;
+    { 1+29; }
 }
 
 #[clippy::msrv = "1.30"]
 fn msrv_1_30() {
     #[cfg_attr(rustfmt, rustfmt::skip)]
-    1+30;
+    { 1+30; }
 }
diff --git a/src/tools/rustfmt/tests/source/attrib.rs b/src/tools/rustfmt/tests/source/attrib.rs
index d45fba55224..fc13cd02b03 100644
--- a/src/tools/rustfmt/tests/source/attrib.rs
+++ b/src/tools/rustfmt/tests/source/attrib.rs
@@ -214,8 +214,8 @@ type Os = NoSource;
 // #3313
 fn stmt_expr_attributes() {
     let foo ;
-    #[must_use]
-   foo = false ;
+    (#[must_use]
+   foo) = false ;
 }
 
 // #3509
diff --git a/src/tools/rustfmt/tests/target/attrib.rs b/src/tools/rustfmt/tests/target/attrib.rs
index 7e61f68d76a..7b3309676de 100644
--- a/src/tools/rustfmt/tests/target/attrib.rs
+++ b/src/tools/rustfmt/tests/target/attrib.rs
@@ -248,8 +248,8 @@ type Os = NoSource;
 // #3313
 fn stmt_expr_attributes() {
     let foo;
-    #[must_use]
-    foo = false;
+    (#[must_use]
+    foo) = false;
 }
 
 // #3509
diff --git a/tests/pretty/ast-stmt-expr-attr.rs b/tests/pretty/ast-stmt-expr-attr.rs
index fd7272a1b1f..899f7173f70 100644
--- a/tests/pretty/ast-stmt-expr-attr.rs
+++ b/tests/pretty/ast-stmt-expr-attr.rs
@@ -13,17 +13,17 @@ fn syntax() {
     let _ = #[attr] ();
     let _ = #[attr] (#[attr] 0,);
     let _ = #[attr] (#[attr] 0, 0);
-    let _ = #[attr] 0 + #[attr] 0;
-    let _ = #[attr] 0 / #[attr] 0;
-    let _ = #[attr] 0 & #[attr] 0;
-    let _ = #[attr] 0 % #[attr] 0;
+    let _ = (#[attr] 0) + #[attr] 0;
+    let _ = (#[attr] 0) / #[attr] 0;
+    let _ = (#[attr] 0) & #[attr] 0;
+    let _ = (#[attr] 0) % #[attr] 0;
     let _ = #[attr] (0 + 0);
     let _ = #[attr] !0;
     let _ = #[attr] -0;
     let _ = #[attr] false;
     let _ = #[attr] 0;
     let _ = #[attr] 'c';
-    let _ = #[attr] x as Y;
+    let _ = (#[attr] x) as Y;
     let _ = #[attr] (x as Y);
     let _ =
         #[attr] while true {
@@ -88,9 +88,9 @@ fn syntax() {
             let _ = ();
             foo
         };
-    let _ = #[attr] x = y;
+    let _ = (#[attr] x) = y;
     let _ = #[attr] (x = y);
-    let _ = #[attr] x += y;
+    let _ = (#[attr] x) += y;
     let _ = #[attr] (x += y);
     let _ = #[attr] foo.bar;
     let _ = (#[attr] foo).bar;
@@ -98,8 +98,8 @@ fn syntax() {
     let _ = (#[attr] foo).0;
     let _ = #[attr] foo[bar];
     let _ = (#[attr] foo)[bar];
-    let _ = #[attr] 0..#[attr] 0;
-    let _ = #[attr] 0..;
+    let _ = (#[attr] 0)..#[attr] 0;
+    let _ = (#[attr] 0)..;
     let _ = #[attr] (0..0);
     let _ = #[attr] (0..);
     let _ = #[attr] (..0);
diff --git a/tests/pretty/stmt_expr_attributes.rs b/tests/pretty/stmt_expr_attributes.rs
index 98ad98b863a..5076adf5aa4 100644
--- a/tests/pretty/stmt_expr_attributes.rs
+++ b/tests/pretty/stmt_expr_attributes.rs
@@ -148,13 +148,13 @@ fn _11() {
     let _ = #[rustc_dummy] (0);
     let _ = #[rustc_dummy] (0,);
     let _ = #[rustc_dummy] (0, 0);
-    let _ = #[rustc_dummy] 0 + #[rustc_dummy] 0;
+    let _ = (#[rustc_dummy] 0) + #[rustc_dummy] 0;
     let _ = #[rustc_dummy] !0;
     let _ = #[rustc_dummy] -0i32;
     let _ = #[rustc_dummy] false;
     let _ = #[rustc_dummy] 'c';
     let _ = #[rustc_dummy] 0;
-    let _ = #[rustc_dummy] 0 as usize;
+    let _ = (#[rustc_dummy] 0) as usize;
     let _ =
         #[rustc_dummy] while false {
             #![rustc_dummy]
@@ -214,8 +214,8 @@ fn _11() {
                 #![rustc_dummy]
             };
     let mut x = 0;
-    let _ = #[rustc_dummy] x = 15;
-    let _ = #[rustc_dummy] x += 15;
+    let _ = (#[rustc_dummy] x) = 15;
+    let _ = (#[rustc_dummy] x) += 15;
     let s = Foo { data: () };
     let _ = #[rustc_dummy] s.data;
     let _ = (#[rustc_dummy] s).data;
@@ -225,8 +225,8 @@ fn _11() {
     let v = vec!(0);
     let _ = #[rustc_dummy] v[0];
     let _ = (#[rustc_dummy] v)[0];
-    let _ = #[rustc_dummy] 0..#[rustc_dummy] 0;
-    let _ = #[rustc_dummy] 0..;
+    let _ = (#[rustc_dummy] 0)..#[rustc_dummy] 0;
+    let _ = (#[rustc_dummy] 0)..;
     let _ = #[rustc_dummy] (0..0);
     let _ = #[rustc_dummy] (0..);
     let _ = #[rustc_dummy] (..0);
diff --git a/tests/ui/lint/unused/unused-doc-comments-edge-cases.rs b/tests/ui/lint/unused/unused-doc-comments-edge-cases.rs
index ba32fb566e8..0f9eac93930 100644
--- a/tests/ui/lint/unused/unused-doc-comments-edge-cases.rs
+++ b/tests/ui/lint/unused/unused-doc-comments-edge-cases.rs
@@ -20,10 +20,10 @@ fn doc_comment_between_if_else(num: u8) -> bool {
 }
 
 fn doc_comment_on_expr(num: u8) -> bool {
-    /// useless doc comment
+    (/// useless doc comment
     //~^ ERROR: attributes on expressions are experimental
     //~| ERROR: unused doc comment
-    num == 3
+    num) == 3
 }
 
 fn doc_comment_on_expr_field() -> bool {
diff --git a/tests/ui/lint/unused/unused-doc-comments-edge-cases.stderr b/tests/ui/lint/unused/unused-doc-comments-edge-cases.stderr
index 55e4834e670..add85b2f5e0 100644
--- a/tests/ui/lint/unused/unused-doc-comments-edge-cases.stderr
+++ b/tests/ui/lint/unused/unused-doc-comments-edge-cases.stderr
@@ -5,10 +5,10 @@ LL |     else {
    |     ^^^^ expected expression
 
 error[E0658]: attributes on expressions are experimental
-  --> $DIR/unused-doc-comments-edge-cases.rs:23:5
+  --> $DIR/unused-doc-comments-edge-cases.rs:23:6
    |
-LL |     /// useless doc comment
-   |     ^^^^^^^^^^^^^^^^^^^^^^^
+LL |     (/// useless doc comment
+   |      ^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
    = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
@@ -32,12 +32,12 @@ LL | #![deny(unused_doc_comments)]
    |         ^^^^^^^^^^^^^^^^^^^
 
 error: unused doc comment
-  --> $DIR/unused-doc-comments-edge-cases.rs:23:5
+  --> $DIR/unused-doc-comments-edge-cases.rs:23:6
    |
-LL |     /// useless doc comment
-   |     ^^^^^^^^^^^^^^^^^^^^^^^
+LL |     (/// useless doc comment
+   |      ^^^^^^^^^^^^^^^^^^^^^^^
 ...
-LL |     num == 3
+LL |     num) == 3
    |     --- rustdoc does not generate documentation for expressions
    |
    = help: use `//` for a plain comment
diff --git a/tests/ui/parser/attribute/attr-binary-expr-ambigous.fixed b/tests/ui/parser/attribute/attr-binary-expr-ambigous.fixed
new file mode 100644
index 00000000000..aae71ede771
--- /dev/null
+++ b/tests/ui/parser/attribute/attr-binary-expr-ambigous.fixed
@@ -0,0 +1,15 @@
+//@ run-rustfix
+#![feature(stmt_expr_attributes)]
+#![allow(unused_assignments, unused_attributes)]
+
+fn main() {
+    let mut x = (#[deprecated] 1) + 2; //~ ERROR ambiguous
+
+    (#[deprecated] x) = 4; //~ ERROR ambiguous
+
+    x = (#[deprecated] 5) as i32; //~ ERROR ambiguous
+
+    let _r = (#[deprecated] 1)..6; //~ ERROR ambiguous
+
+    println!("{}", x);
+}
diff --git a/tests/ui/parser/attribute/attr-binary-expr-ambigous.rs b/tests/ui/parser/attribute/attr-binary-expr-ambigous.rs
new file mode 100644
index 00000000000..613e01d743b
--- /dev/null
+++ b/tests/ui/parser/attribute/attr-binary-expr-ambigous.rs
@@ -0,0 +1,15 @@
+//@ run-rustfix
+#![feature(stmt_expr_attributes)]
+#![allow(unused_assignments, unused_attributes)]
+
+fn main() {
+    let mut x = #[deprecated] 1 + 2; //~ ERROR ambiguous
+
+    #[deprecated] x = 4; //~ ERROR ambiguous
+
+    x = #[deprecated] 5 as i32; //~ ERROR ambiguous
+
+    let _r = #[deprecated] 1..6; //~ ERROR ambiguous
+
+    println!("{}", x);
+}
diff --git a/tests/ui/parser/attribute/attr-binary-expr-ambigous.stderr b/tests/ui/parser/attribute/attr-binary-expr-ambigous.stderr
new file mode 100644
index 00000000000..0430570fd49
--- /dev/null
+++ b/tests/ui/parser/attribute/attr-binary-expr-ambigous.stderr
@@ -0,0 +1,46 @@
+error: ambiguous outer attributes
+  --> $DIR/attr-binary-expr-ambigous.rs:6:17
+   |
+LL |     let mut x = #[deprecated] 1 + 2;
+   |                 ^^^^^^^^^^^^^^^^^^^
+   |
+help: wrap the expression in parentheses
+   |
+LL |     let mut x = (#[deprecated] 1) + 2;
+   |                 +               +
+
+error: ambiguous outer attributes
+  --> $DIR/attr-binary-expr-ambigous.rs:8:5
+   |
+LL |     #[deprecated] x = 4;
+   |     ^^^^^^^^^^^^^^^^^^^
+   |
+help: wrap the expression in parentheses
+   |
+LL |     (#[deprecated] x) = 4;
+   |     +               +
+
+error: ambiguous outer attributes
+  --> $DIR/attr-binary-expr-ambigous.rs:10:9
+   |
+LL |     x = #[deprecated] 5 as i32;
+   |         ^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: wrap the expression in parentheses
+   |
+LL |     x = (#[deprecated] 5) as i32;
+   |         +               +
+
+error: ambiguous outer attributes
+  --> $DIR/attr-binary-expr-ambigous.rs:12:14
+   |
+LL |     let _r = #[deprecated] 1..6;
+   |              ^^^^^^^^^^^^^^^^^^
+   |
+help: wrap the expression in parentheses
+   |
+LL |     let _r = (#[deprecated] 1)..6;
+   |              +               +
+
+error: aborting due to 4 previous errors
+
diff --git a/tests/ui/proc-macro/issue-81555.rs b/tests/ui/proc-macro/issue-81555.rs
index 7a61a31952f..b337ab7ce37 100644
--- a/tests/ui/proc-macro/issue-81555.rs
+++ b/tests/ui/proc-macro/issue-81555.rs
@@ -10,6 +10,5 @@ use test_macros::identity_attr;
 fn main() {
     let _x;
     let y = ();
-    #[identity_attr]
-    _x = y;
+    (#[identity_attr] _x) = y;
 }