about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLeón Orell Valerian Liehr <me@fmease.dev>2025-06-15 23:51:54 +0200
committerGitHub <noreply@github.com>2025-06-15 23:51:54 +0200
commitb79d3b1ec15c64bf00677207287b17e3d6f6e05f (patch)
tree10b4b523937e18bf1aaed71cb86d5916128db66d
parentd6dc9656ea43fed1f862b36a68caffd631194654 (diff)
parent2171f89eb223a4f5f3af74df24104d77c1a8a6aa (diff)
downloadrust-b79d3b1ec15c64bf00677207287b17e3d6f6e05f.tar.gz
rust-b79d3b1ec15c64bf00677207287b17e3d6f6e05f.zip
Rollup merge of #134661 - dtolnay:prefixattr, r=fmease
Reduce precedence of expressions that have an outer attr

Previously, `-Zunpretty=expanded` would expand this program as follows:

```rust
#![feature(stmt_expr_attributes)]

macro_rules! repro {
    ($e:expr) => {
        #[allow(deprecated)] $e
    };
}

#[derive(Default)]
struct Thing {
    #[deprecated]
    field: i32,
}

fn main() {
    let thing = Thing::default();
    let _ = repro!(thing).field;
}
```

```rs
#![feature(prelude_import)]
#![feature(stmt_expr_attributes)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;

struct Thing {
    #[deprecated]
    field: i32,
}

#[automatically_derived]
impl ::core::default::Default for Thing {
    #[inline]
    fn default() -> Thing {
        Thing { field: ::core::default::Default::default() }
    }
}

fn main() {
    let thing = Thing::default();
    let _ = #[allow(deprecated)] thing.field;
}
```

This is not the correct expansion. The correct output would have `(#[allow(deprecated)] thing).field` with the attribute applying only to `thing`, not to `thing.field`.
-rw-r--r--compiler/rustc_ast/src/ast.rs17
-rw-r--r--compiler/rustc_hir/src/hir.rs21
-rw-r--r--compiler/rustc_hir_pretty/src/lib.rs52
-rw-r--r--compiler/rustc_hir_typeck/src/callee.rs2
-rw-r--r--compiler/rustc_hir_typeck/src/cast.rs2
-rw-r--r--compiler/rustc_hir_typeck/src/expr.rs27
-rw-r--r--compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs8
-rw-r--r--compiler/rustc_lint/src/context.rs15
-rw-r--r--src/tools/clippy/clippy_lints/src/casts/unnecessary_cast.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/dereference.rs10
-rw-r--r--src/tools/clippy/clippy_lints/src/loops/single_element_loop.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/matches/manual_utils.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/neg_multiply.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/redundant_slicing.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs2
-rw-r--r--tests/ui-fulldeps/auxiliary/parser.rs42
-rw-r--r--tests/ui-fulldeps/pprust-parenthesis-insertion.rs5
17 files changed, 164 insertions, 49 deletions
diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs
index 6b51cbd7fbe..4489a424c0d 100644
--- a/compiler/rustc_ast/src/ast.rs
+++ b/compiler/rustc_ast/src/ast.rs
@@ -1450,11 +1450,20 @@ impl Expr {
     }
 
     pub fn precedence(&self) -> ExprPrecedence {
+        fn prefix_attrs_precedence(attrs: &AttrVec) -> ExprPrecedence {
+            for attr in attrs {
+                if let AttrStyle::Outer = attr.style {
+                    return ExprPrecedence::Prefix;
+                }
+            }
+            ExprPrecedence::Unambiguous
+        }
+
         match &self.kind {
             ExprKind::Closure(closure) => {
                 match closure.fn_decl.output {
                     FnRetTy::Default(_) => ExprPrecedence::Jump,
-                    FnRetTy::Ty(_) => ExprPrecedence::Unambiguous,
+                    FnRetTy::Ty(_) => prefix_attrs_precedence(&self.attrs),
                 }
             }
 
@@ -1463,7 +1472,7 @@ impl Expr {
             | ExprKind::Yield(YieldKind::Prefix(value))
             | ExprKind::Yeet(value) => match value {
                 Some(_) => ExprPrecedence::Jump,
-                None => ExprPrecedence::Unambiguous,
+                None => prefix_attrs_precedence(&self.attrs),
             },
 
             ExprKind::Become(_) => ExprPrecedence::Jump,
@@ -1490,7 +1499,7 @@ impl Expr {
             | ExprKind::Let(..)
             | ExprKind::Unary(..) => ExprPrecedence::Prefix,
 
-            // Never need parens
+            // Need parens if and only if there are prefix attributes.
             ExprKind::Array(_)
             | ExprKind::Await(..)
             | ExprKind::Use(..)
@@ -1525,7 +1534,7 @@ impl Expr {
             | ExprKind::While(..)
             | ExprKind::Yield(YieldKind::Postfix(..))
             | ExprKind::Err(_)
-            | ExprKind::Dummy => ExprPrecedence::Unambiguous,
+            | ExprKind::Dummy => prefix_attrs_precedence(&self.attrs),
         }
     }
 
diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs
index 3cc6bde8c32..556f50a85af 100644
--- a/compiler/rustc_hir/src/hir.rs
+++ b/compiler/rustc_hir/src/hir.rs
@@ -2285,12 +2285,23 @@ pub struct Expr<'hir> {
 }
 
 impl Expr<'_> {
-    pub fn precedence(&self) -> ExprPrecedence {
+    pub fn precedence(
+        &self,
+        for_each_attr: &dyn Fn(HirId, &mut dyn FnMut(&Attribute)),
+    ) -> ExprPrecedence {
+        let prefix_attrs_precedence = || -> ExprPrecedence {
+            let mut has_outer_attr = false;
+            for_each_attr(self.hir_id, &mut |attr: &Attribute| {
+                has_outer_attr |= matches!(attr.style(), AttrStyle::Outer)
+            });
+            if has_outer_attr { ExprPrecedence::Prefix } else { ExprPrecedence::Unambiguous }
+        };
+
         match &self.kind {
             ExprKind::Closure(closure) => {
                 match closure.fn_decl.output {
                     FnRetTy::DefaultReturn(_) => ExprPrecedence::Jump,
-                    FnRetTy::Return(_) => ExprPrecedence::Unambiguous,
+                    FnRetTy::Return(_) => prefix_attrs_precedence(),
                 }
             }
 
@@ -2315,7 +2326,7 @@ impl Expr<'_> {
             | ExprKind::Let(..)
             | ExprKind::Unary(..) => ExprPrecedence::Prefix,
 
-            // Never need parens
+            // Need parens if and only if there are prefix attributes.
             ExprKind::Array(_)
             | ExprKind::Block(..)
             | ExprKind::Call(..)
@@ -2337,9 +2348,9 @@ impl Expr<'_> {
             | ExprKind::Type(..)
             | ExprKind::UnsafeBinderCast(..)
             | ExprKind::Use(..)
-            | ExprKind::Err(_) => ExprPrecedence::Unambiguous,
+            | ExprKind::Err(_) => prefix_attrs_precedence(),
 
-            ExprKind::DropTemps(expr, ..) => expr.precedence(),
+            ExprKind::DropTemps(expr, ..) => expr.precedence(for_each_attr),
         }
     }
 
diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs
index b23b3125c59..fc507285860 100644
--- a/compiler/rustc_hir_pretty/src/lib.rs
+++ b/compiler/rustc_hir_pretty/src/lib.rs
@@ -80,6 +80,13 @@ impl<'a> State<'a> {
         (self.attrs)(id)
     }
 
+    fn precedence(&self, expr: &hir::Expr<'_>) -> ExprPrecedence {
+        let for_each_attr = |id: HirId, callback: &mut dyn FnMut(&hir::Attribute)| {
+            self.attrs(id).iter().for_each(callback);
+        };
+        expr.precedence(&for_each_attr)
+    }
+
     fn print_attrs_as_inner(&mut self, attrs: &[hir::Attribute]) {
         self.print_either_attributes(attrs, ast::AttrStyle::Inner)
     }
@@ -1164,7 +1171,7 @@ impl<'a> State<'a> {
         }
         self.space();
         self.word_space("=");
-        let npals = || parser::needs_par_as_let_scrutinee(init.precedence());
+        let npals = || parser::needs_par_as_let_scrutinee(self.precedence(init));
         self.print_expr_cond_paren(init, Self::cond_needs_par(init) || npals())
     }
 
@@ -1265,7 +1272,7 @@ impl<'a> State<'a> {
     fn print_expr_call(&mut self, func: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
         let needs_paren = match func.kind {
             hir::ExprKind::Field(..) => true,
-            _ => func.precedence() < ExprPrecedence::Unambiguous,
+            _ => self.precedence(func) < ExprPrecedence::Unambiguous,
         };
 
         self.print_expr_cond_paren(func, needs_paren);
@@ -1279,7 +1286,10 @@ impl<'a> State<'a> {
         args: &[hir::Expr<'_>],
     ) {
         let base_args = args;
-        self.print_expr_cond_paren(receiver, receiver.precedence() < ExprPrecedence::Unambiguous);
+        self.print_expr_cond_paren(
+            receiver,
+            self.precedence(receiver) < ExprPrecedence::Unambiguous,
+        );
         self.word(".");
         self.print_ident(segment.ident);
 
@@ -1293,8 +1303,8 @@ impl<'a> State<'a> {
 
     fn print_expr_binary(&mut self, op: hir::BinOpKind, lhs: &hir::Expr<'_>, rhs: &hir::Expr<'_>) {
         let binop_prec = op.precedence();
-        let left_prec = lhs.precedence();
-        let right_prec = rhs.precedence();
+        let left_prec = self.precedence(lhs);
+        let right_prec = self.precedence(rhs);
 
         let (mut left_needs_paren, right_needs_paren) = match op.fixity() {
             Fixity::Left => (left_prec < binop_prec, right_prec <= binop_prec),
@@ -1323,7 +1333,7 @@ impl<'a> State<'a> {
 
     fn print_expr_unary(&mut self, op: hir::UnOp, expr: &hir::Expr<'_>) {
         self.word(op.as_str());
-        self.print_expr_cond_paren(expr, expr.precedence() < ExprPrecedence::Prefix);
+        self.print_expr_cond_paren(expr, self.precedence(expr) < ExprPrecedence::Prefix);
     }
 
     fn print_expr_addr_of(
@@ -1340,7 +1350,7 @@ impl<'a> State<'a> {
                 self.print_mutability(mutability, true);
             }
         }
-        self.print_expr_cond_paren(expr, expr.precedence() < ExprPrecedence::Prefix);
+        self.print_expr_cond_paren(expr, self.precedence(expr) < ExprPrecedence::Prefix);
     }
 
     fn print_literal(&mut self, lit: &hir::Lit) {
@@ -1483,7 +1493,7 @@ impl<'a> State<'a> {
                 self.print_literal(lit);
             }
             hir::ExprKind::Cast(expr, ty) => {
-                self.print_expr_cond_paren(expr, expr.precedence() < ExprPrecedence::Cast);
+                self.print_expr_cond_paren(expr, self.precedence(expr) < ExprPrecedence::Cast);
                 self.space();
                 self.word_space("as");
                 self.print_type(ty);
@@ -1580,24 +1590,30 @@ impl<'a> State<'a> {
                 self.print_block(blk, cb, ib);
             }
             hir::ExprKind::Assign(lhs, rhs, _) => {
-                self.print_expr_cond_paren(lhs, lhs.precedence() <= ExprPrecedence::Assign);
+                self.print_expr_cond_paren(lhs, self.precedence(lhs) <= ExprPrecedence::Assign);
                 self.space();
                 self.word_space("=");
-                self.print_expr_cond_paren(rhs, rhs.precedence() < ExprPrecedence::Assign);
+                self.print_expr_cond_paren(rhs, self.precedence(rhs) < ExprPrecedence::Assign);
             }
             hir::ExprKind::AssignOp(op, lhs, rhs) => {
-                self.print_expr_cond_paren(lhs, lhs.precedence() <= ExprPrecedence::Assign);
+                self.print_expr_cond_paren(lhs, self.precedence(lhs) <= ExprPrecedence::Assign);
                 self.space();
                 self.word_space(op.node.as_str());
-                self.print_expr_cond_paren(rhs, rhs.precedence() < ExprPrecedence::Assign);
+                self.print_expr_cond_paren(rhs, self.precedence(rhs) < ExprPrecedence::Assign);
             }
             hir::ExprKind::Field(expr, ident) => {
-                self.print_expr_cond_paren(expr, expr.precedence() < ExprPrecedence::Unambiguous);
+                self.print_expr_cond_paren(
+                    expr,
+                    self.precedence(expr) < ExprPrecedence::Unambiguous,
+                );
                 self.word(".");
                 self.print_ident(ident);
             }
             hir::ExprKind::Index(expr, index, _) => {
-                self.print_expr_cond_paren(expr, expr.precedence() < ExprPrecedence::Unambiguous);
+                self.print_expr_cond_paren(
+                    expr,
+                    self.precedence(expr) < ExprPrecedence::Unambiguous,
+                );
                 self.word("[");
                 self.print_expr(index);
                 self.word("]");
@@ -1611,7 +1627,7 @@ impl<'a> State<'a> {
                 }
                 if let Some(expr) = opt_expr {
                     self.space();
-                    self.print_expr_cond_paren(expr, expr.precedence() < ExprPrecedence::Jump);
+                    self.print_expr_cond_paren(expr, self.precedence(expr) < ExprPrecedence::Jump);
                 }
             }
             hir::ExprKind::Continue(destination) => {
@@ -1625,13 +1641,13 @@ impl<'a> State<'a> {
                 self.word("return");
                 if let Some(expr) = result {
                     self.word(" ");
-                    self.print_expr_cond_paren(expr, expr.precedence() < ExprPrecedence::Jump);
+                    self.print_expr_cond_paren(expr, self.precedence(expr) < ExprPrecedence::Jump);
                 }
             }
             hir::ExprKind::Become(result) => {
                 self.word("become");
                 self.word(" ");
-                self.print_expr_cond_paren(result, result.precedence() < ExprPrecedence::Jump);
+                self.print_expr_cond_paren(result, self.precedence(result) < ExprPrecedence::Jump);
             }
             hir::ExprKind::InlineAsm(asm) => {
                 self.word("asm!");
@@ -1669,7 +1685,7 @@ impl<'a> State<'a> {
             }
             hir::ExprKind::Yield(expr, _) => {
                 self.word_space("yield");
-                self.print_expr_cond_paren(expr, expr.precedence() < ExprPrecedence::Jump);
+                self.print_expr_cond_paren(expr, self.precedence(expr) < ExprPrecedence::Jump);
             }
             hir::ExprKind::Err(_) => {
                 self.popen();
diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs
index e915b4fc626..80bff09d0a4 100644
--- a/compiler/rustc_hir_typeck/src/callee.rs
+++ b/compiler/rustc_hir_typeck/src/callee.rs
@@ -620,7 +620,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             };
 
             if let Ok(rest_snippet) = rest_snippet {
-                let sugg = if callee_expr.precedence() >= ExprPrecedence::Unambiguous {
+                let sugg = if self.precedence(callee_expr) >= ExprPrecedence::Unambiguous {
                     vec![
                         (up_to_rcvr_span, "".to_string()),
                         (rest_span, format!(".{}({rest_snippet}", segment.ident)),
diff --git a/compiler/rustc_hir_typeck/src/cast.rs b/compiler/rustc_hir_typeck/src/cast.rs
index e17cfc15a43..6f8abc1e67d 100644
--- a/compiler/rustc_hir_typeck/src/cast.rs
+++ b/compiler/rustc_hir_typeck/src/cast.rs
@@ -1100,7 +1100,7 @@ impl<'a, 'tcx> CastCheck<'tcx> {
     }
 
     fn lossy_provenance_ptr2int_lint(&self, fcx: &FnCtxt<'a, 'tcx>, t_c: ty::cast::IntTy) {
-        let expr_prec = self.expr.precedence();
+        let expr_prec = fcx.precedence(self.expr);
         let needs_parens = expr_prec < ExprPrecedence::Unambiguous;
 
         let needs_cast = !matches!(t_c, ty::cast::IntTy::U(ty::UintTy::Usize));
diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs
index 30bf557dc93..55c39d960e7 100644
--- a/compiler/rustc_hir_typeck/src/expr.rs
+++ b/compiler/rustc_hir_typeck/src/expr.rs
@@ -6,6 +6,7 @@
 //! See [`rustc_hir_analysis::check`] for more context on type checking in general.
 
 use rustc_abi::{ExternAbi, FIRST_VARIANT, FieldIdx};
+use rustc_ast::util::parser::ExprPrecedence;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_data_structures::stack::ensure_sufficient_stack;
 use rustc_data_structures::unord::UnordMap;
@@ -17,7 +18,7 @@ use rustc_errors::{
 use rustc_hir::def::{CtorKind, DefKind, Res};
 use rustc_hir::def_id::DefId;
 use rustc_hir::lang_items::LangItem;
-use rustc_hir::{ExprKind, HirId, QPath};
+use rustc_hir::{Attribute, ExprKind, HirId, QPath};
 use rustc_hir_analysis::NoVariantNamed;
 use rustc_hir_analysis::hir_ty_lowering::{FeedConstTy, HirTyLowerer as _};
 use rustc_infer::infer;
@@ -54,6 +55,30 @@ use crate::{
 };
 
 impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
+    pub(crate) fn precedence(&self, expr: &hir::Expr<'_>) -> ExprPrecedence {
+        let for_each_attr = |id: HirId, callback: &mut dyn FnMut(&Attribute)| {
+            for attr in self.tcx.hir_attrs(id) {
+                // For the purpose of rendering suggestions, disregard attributes
+                // that originate from desugaring of any kind. For example, `x?`
+                // desugars to `#[allow(unreachable_code)] match ...`. Failing to
+                // ignore the prefix attribute in the desugaring would cause this
+                // suggestion:
+                //
+                //     let y: u32 = x?.try_into().unwrap();
+                //                    ++++++++++++++++++++
+                //
+                // to be rendered as:
+                //
+                //     let y: u32 = (x?).try_into().unwrap();
+                //                  +  +++++++++++++++++++++
+                if attr.span().desugaring_kind().is_none() {
+                    callback(attr);
+                }
+            }
+        };
+        expr.precedence(&for_each_attr)
+    }
+
     /// Check an expr with an expectation type, and also demand that the expr's
     /// evaluated type is a subtype of the expectation at the end. This is a
     /// *hard* requirement.
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
index 66af085cfd4..7e5f1d97a8b 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
@@ -399,7 +399,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     // so we remove the user's `clone` call.
                     {
                         vec![(receiver_method.ident.span, conversion_method_name.to_string())]
-                    } else if expr.precedence() < ExprPrecedence::Unambiguous {
+                    } else if self.precedence(expr) < ExprPrecedence::Unambiguous {
                         vec![
                             (expr.span.shrink_to_lo(), "(".to_string()),
                             (expr.span.shrink_to_hi(), format!(").{}()", conversion_method_name)),
@@ -1395,7 +1395,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         {
             let span = expr.span.find_oldest_ancestor_in_same_ctxt();
 
-            let mut sugg = if expr.precedence() >= ExprPrecedence::Unambiguous {
+            let mut sugg = if self.precedence(expr) >= ExprPrecedence::Unambiguous {
                 vec![(span.shrink_to_hi(), ".into()".to_owned())]
             } else {
                 vec![
@@ -3106,7 +3106,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             "change the type of the numeric literal from `{checked_ty}` to `{expected_ty}`",
         );
 
-        let close_paren = if expr.precedence() < ExprPrecedence::Unambiguous {
+        let close_paren = if self.precedence(expr) < ExprPrecedence::Unambiguous {
             sugg.push((expr.span.shrink_to_lo(), "(".to_string()));
             ")"
         } else {
@@ -3131,7 +3131,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 let len = src.trim_end_matches(&checked_ty.to_string()).len();
                 span.with_lo(span.lo() + BytePos(len as u32))
             },
-            if expr.precedence() < ExprPrecedence::Unambiguous {
+            if self.precedence(expr) < ExprPrecedence::Unambiguous {
                 // Readd `)`
                 format!("{expected_ty})")
             } else {
diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs
index 5679d4566dc..b6bf45dfbcf 100644
--- a/compiler/rustc_lint/src/context.rs
+++ b/compiler/rustc_lint/src/context.rs
@@ -7,6 +7,7 @@ use std::cell::Cell;
 use std::slice;
 
 use rustc_ast::BindingMode;
+use rustc_ast::util::parser::ExprPrecedence;
 use rustc_data_structures::fx::FxIndexMap;
 use rustc_data_structures::sync;
 use rustc_data_structures::unord::UnordMap;
@@ -850,6 +851,20 @@ impl<'tcx> LateContext<'tcx> {
             })
     }
 
+    /// Returns the effective precedence of an expression for the purpose of
+    /// rendering diagnostic. This is not the same as the precedence that would
+    /// be used for pretty-printing HIR by rustc_hir_pretty.
+    pub fn precedence(&self, expr: &hir::Expr<'_>) -> ExprPrecedence {
+        let for_each_attr = |id: hir::HirId, callback: &mut dyn FnMut(&hir::Attribute)| {
+            for attr in self.tcx.hir_attrs(id) {
+                if attr.span().desugaring_kind().is_none() {
+                    callback(attr);
+                }
+            }
+        };
+        expr.precedence(&for_each_attr)
+    }
+
     /// If the given expression is a local binding, find the initializer expression.
     /// If that initializer expression is another local binding, find its initializer again.
     ///
diff --git a/src/tools/clippy/clippy_lints/src/casts/unnecessary_cast.rs b/src/tools/clippy/clippy_lints/src/casts/unnecessary_cast.rs
index 8e8c55cf383..010f09d4c1d 100644
--- a/src/tools/clippy/clippy_lints/src/casts/unnecessary_cast.rs
+++ b/src/tools/clippy/clippy_lints/src/casts/unnecessary_cast.rs
@@ -185,7 +185,7 @@ pub(super) fn check<'tcx>(
             Node::Expr(parent) if is_borrow_expr(cx, parent) && !is_in_allowed_macro(cx, parent) => {
                 MaybeParenOrBlock::Block
             },
-            Node::Expr(parent) if cast_expr.precedence() < parent.precedence() => MaybeParenOrBlock::Paren,
+            Node::Expr(parent) if cx.precedence(cast_expr) < cx.precedence(parent) => MaybeParenOrBlock::Paren,
             _ => MaybeParenOrBlock::Nothing,
         };
 
diff --git a/src/tools/clippy/clippy_lints/src/dereference.rs b/src/tools/clippy/clippy_lints/src/dereference.rs
index cde9528cd87..7463d7b5c3b 100644
--- a/src/tools/clippy/clippy_lints/src/dereference.rs
+++ b/src/tools/clippy/clippy_lints/src/dereference.rs
@@ -972,7 +972,7 @@ fn report<'tcx>(
                 "&"
             };
 
-            let expr_str = if !expr_is_macro_call && is_ufcs && expr.precedence() < ExprPrecedence::Prefix {
+            let expr_str = if !expr_is_macro_call && is_ufcs && cx.precedence(expr) < ExprPrecedence::Prefix {
                 Cow::Owned(format!("({expr_str})"))
             } else {
                 expr_str
@@ -1015,10 +1015,10 @@ fn report<'tcx>(
                         Node::Expr(e) => match e.kind {
                             ExprKind::Call(callee, _) if callee.hir_id != data.first_expr.hir_id => false,
                             ExprKind::Call(..) => {
-                                expr.precedence() < ExprPrecedence::Unambiguous
+                                cx.precedence(expr) < ExprPrecedence::Unambiguous
                                     || matches!(expr.kind, ExprKind::Field(..))
                             },
-                            _ => expr.precedence() < e.precedence(),
+                            _ => cx.precedence(expr) < cx.precedence(e),
                         },
                         _ => false,
                     };
@@ -1066,7 +1066,7 @@ fn report<'tcx>(
                         Mutability::Not => "&",
                         Mutability::Mut => "&mut ",
                     };
-                    (prefix, expr.precedence() < ExprPrecedence::Prefix)
+                    (prefix, cx.precedence(expr) < ExprPrecedence::Prefix)
                 },
                 None if !ty.is_ref() && data.adjusted_ty.is_ref() => ("&", false),
                 _ => ("", false),
@@ -1172,7 +1172,7 @@ impl<'tcx> Dereferencing<'tcx> {
                 },
                 Some(parent) if !parent.span.from_expansion() => {
                     // Double reference might be needed at this point.
-                    if parent.precedence() == ExprPrecedence::Unambiguous {
+                    if cx.precedence(parent) == ExprPrecedence::Unambiguous {
                         // Parentheses would be needed here, don't lint.
                         *outer_pat = None;
                     } else {
diff --git a/src/tools/clippy/clippy_lints/src/loops/single_element_loop.rs b/src/tools/clippy/clippy_lints/src/loops/single_element_loop.rs
index 12719c4f94b..d66771a8b5b 100644
--- a/src/tools/clippy/clippy_lints/src/loops/single_element_loop.rs
+++ b/src/tools/clippy/clippy_lints/src/loops/single_element_loop.rs
@@ -84,7 +84,7 @@ pub(super) fn check<'tcx>(
         if !prefix.is_empty()
             && (
                 // Precedence of internal expression is less than or equal to precedence of `&expr`.
-                arg_expression.precedence() <= ExprPrecedence::Prefix || is_range_literal(arg_expression)
+                cx.precedence(arg_expression) <= ExprPrecedence::Prefix || is_range_literal(arg_expression)
             )
         {
             arg_snip = format!("({arg_snip})").into();
diff --git a/src/tools/clippy/clippy_lints/src/matches/manual_utils.rs b/src/tools/clippy/clippy_lints/src/matches/manual_utils.rs
index d0905733ab5..dbae71bbb1b 100644
--- a/src/tools/clippy/clippy_lints/src/matches/manual_utils.rs
+++ b/src/tools/clippy/clippy_lints/src/matches/manual_utils.rs
@@ -117,7 +117,7 @@ where
     // it's being passed by value.
     let scrutinee = peel_hir_expr_refs(scrutinee).0;
     let (scrutinee_str, _) = snippet_with_context(cx, scrutinee.span, expr_ctxt, "..", &mut app);
-    let scrutinee_str = if scrutinee.span.eq_ctxt(expr.span) && scrutinee.precedence() < ExprPrecedence::Unambiguous {
+    let scrutinee_str = if scrutinee.span.eq_ctxt(expr.span) && cx.precedence(scrutinee) < ExprPrecedence::Unambiguous {
         format!("({scrutinee_str})")
     } else {
         scrutinee_str.into()
diff --git a/src/tools/clippy/clippy_lints/src/neg_multiply.rs b/src/tools/clippy/clippy_lints/src/neg_multiply.rs
index 74c8142787e..442280f9998 100644
--- a/src/tools/clippy/clippy_lints/src/neg_multiply.rs
+++ b/src/tools/clippy/clippy_lints/src/neg_multiply.rs
@@ -64,7 +64,7 @@ fn check_mul(cx: &LateContext<'_>, span: Span, lit: &Expr<'_>, exp: &Expr<'_>) {
     {
         let mut applicability = Applicability::MachineApplicable;
         let (snip, from_macro) = snippet_with_context(cx, exp.span, span.ctxt(), "..", &mut applicability);
-        let suggestion = if !from_macro && exp.precedence() < ExprPrecedence::Prefix && !has_enclosing_paren(&snip) {
+        let suggestion = if !from_macro && cx.precedence(exp) < ExprPrecedence::Prefix && !has_enclosing_paren(&snip) {
             format!("-({snip})")
         } else {
             format!("-{snip}")
diff --git a/src/tools/clippy/clippy_lints/src/redundant_slicing.rs b/src/tools/clippy/clippy_lints/src/redundant_slicing.rs
index 1117dea703c..324a05cdcc0 100644
--- a/src/tools/clippy/clippy_lints/src/redundant_slicing.rs
+++ b/src/tools/clippy/clippy_lints/src/redundant_slicing.rs
@@ -86,7 +86,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing {
             let (indexed_ty, indexed_ref_count) = peel_middle_ty_refs(cx.typeck_results().expr_ty(indexed));
             let parent_expr = get_parent_expr(cx, expr);
             let needs_parens_for_prefix =
-                parent_expr.is_some_and(|parent| parent.precedence() > ExprPrecedence::Prefix);
+                parent_expr.is_some_and(|parent| cx.precedence(parent) > ExprPrecedence::Prefix);
 
             if expr_ty == indexed_ty {
                 if expr_ref_count > indexed_ref_count {
diff --git a/src/tools/clippy/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs b/src/tools/clippy/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs
index 0d5cf45a5e6..18897fbb5b8 100644
--- a/src/tools/clippy/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs
+++ b/src/tools/clippy/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs
@@ -44,7 +44,7 @@ pub(super) fn check<'tcx>(
     };
 
     if let Node::Expr(parent) = cx.tcx.parent_hir_node(e.hir_id)
-        && parent.precedence() > ExprPrecedence::Cast
+        && cx.precedence(parent) > ExprPrecedence::Cast
     {
         sugg = format!("({sugg})");
     }
diff --git a/tests/ui-fulldeps/auxiliary/parser.rs b/tests/ui-fulldeps/auxiliary/parser.rs
index be51bd29008..8a370512460 100644
--- a/tests/ui-fulldeps/auxiliary/parser.rs
+++ b/tests/ui-fulldeps/auxiliary/parser.rs
@@ -7,15 +7,17 @@ extern crate rustc_parse;
 extern crate rustc_session;
 extern crate rustc_span;
 
-use rustc_ast::ast::{DUMMY_NODE_ID, Expr};
-use rustc_ast::mut_visit::MutVisitor;
+use rustc_ast::ast::{AttrKind, Attribute, DUMMY_NODE_ID, Expr};
+use rustc_ast::mut_visit::{self, MutVisitor};
 use rustc_ast::node_id::NodeId;
 use rustc_ast::ptr::P;
-use rustc_ast::token;
+use rustc_ast::token::{self, Token};
+use rustc_ast::tokenstream::{AttrTokenStream, AttrTokenTree, LazyAttrTokenStream};
 use rustc_errors::Diag;
 use rustc_parse::parser::Recovery;
 use rustc_session::parse::ParseSess;
-use rustc_span::{DUMMY_SP, FileName, Span};
+use rustc_span::{AttrId, DUMMY_SP, FileName, Span};
+use std::sync::Arc;
 
 pub fn parse_expr(psess: &ParseSess, source_code: &str) -> Option<P<Expr>> {
     let parser = rustc_parse::unwrap_or_emit_fatal(rustc_parse::new_parser_from_source_str(
@@ -46,4 +48,36 @@ impl MutVisitor for Normalize {
     fn visit_span(&mut self, span: &mut Span) {
         *span = DUMMY_SP;
     }
+
+    fn visit_attribute(&mut self, attr: &mut Attribute) {
+        attr.id = AttrId::from_u32(0);
+        if let AttrKind::Normal(normal_attr) = &mut attr.kind {
+            if let Some(tokens) = &mut normal_attr.tokens {
+                let mut stream = tokens.to_attr_token_stream();
+                normalize_attr_token_stream(&mut stream);
+                *tokens = LazyAttrTokenStream::new_direct(stream);
+            }
+        }
+        mut_visit::walk_attribute(self, attr);
+    }
+}
+
+fn normalize_attr_token_stream(stream: &mut AttrTokenStream) {
+    Arc::make_mut(&mut stream.0)
+        .iter_mut()
+        .for_each(normalize_attr_token_tree);
+}
+
+fn normalize_attr_token_tree(token: &mut AttrTokenTree) {
+    match token {
+        AttrTokenTree::Token(token, _spacing) => {
+            Normalize.visit_span(&mut token.span);
+        }
+        AttrTokenTree::Delimited(dspan, _spacing, _delim, stream) => {
+            normalize_attr_token_stream(stream);
+            Normalize.visit_span(&mut dspan.open);
+            Normalize.visit_span(&mut dspan.close);
+        }
+        AttrTokenTree::AttrsTarget(_) => unimplemented!("AttrTokenTree::AttrsTarget"),
+    }
 }
diff --git a/tests/ui-fulldeps/pprust-parenthesis-insertion.rs b/tests/ui-fulldeps/pprust-parenthesis-insertion.rs
index 36916e97856..08bed40abe8 100644
--- a/tests/ui-fulldeps/pprust-parenthesis-insertion.rs
+++ b/tests/ui-fulldeps/pprust-parenthesis-insertion.rs
@@ -88,6 +88,11 @@ static EXPRS: &[&str] = &[
     // expressions.
     "match 2 { _ => 1 - 1 }",
     "match 2 { _ => ({ 1 }) - 1 }",
+    // Expressions with an outer attr have lower precedence than expressions
+    // with an inner attr.
+    "#[attr] loop {}.field",
+    "(#[attr] loop {}).field",
+    "loop { #![attr] }.field",
     // Grammar restriction: break value starting with a labeled loop is not
     // allowed, except if the break is also labeled.
     "break 'outer 'inner: loop {} + 2",