]> git.lizzy.rs Git - rust.git/blobdiff - clippy_lints/src/dereference.rs
Auto merge of #9148 - arieluy:then_some_unwrap_or, r=Jarcho
[rust.git] / clippy_lints / src / dereference.rs
index bb0b04b6366d46f5589f1dde936f99fa41e4f602..8c7cf7748be1359440197d3692fb6daaf677c024 100644 (file)
@@ -2,20 +2,23 @@
 use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
 use clippy_utils::sugg::has_enclosing_paren;
 use clippy_utils::ty::{expr_sig, peel_mid_ty_refs, variant_of_res};
-use clippy_utils::{get_parent_expr, get_parent_node, is_lint_allowed, path_to_local, walk_to_expr_usage};
+use clippy_utils::{get_parent_expr, is_lint_allowed, path_to_local, walk_to_expr_usage};
 use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
 use rustc_data_structures::fx::FxIndexMap;
 use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_ty, Visitor};
 use rustc_hir::{
-    self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Expr, ExprKind, FnRetTy, GenericArg, HirId, ImplItem,
-    ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, TraitItem,
+    self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Closure, Expr, ExprKind, FnRetTy, GenericArg, HirId,
+    ImplItem, ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, TraitItem,
     TraitItemKind, TyKind, UnOp,
 };
+use rustc_infer::infer::TyCtxtInferExt;
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
-use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeckResults};
+use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitable, TypeckResults};
 use rustc_session::{declare_tool_lint, impl_lint_pass};
-use rustc_span::{symbol::sym, Span};
+use rustc_span::{symbol::sym, Span, Symbol};
+use rustc_trait_selection::infer::InferCtxtExt;
 
 declare_clippy_lint! {
     /// ### What it does
@@ -161,6 +164,12 @@ struct StateData {
     /// Span of the top level expression
     span: Span,
     hir_id: HirId,
+    position: Position,
+}
+
+struct DerefedBorrow {
+    count: usize,
+    msg: &'static str,
 }
 
 enum State {
@@ -172,14 +181,13 @@ enum State {
         /// The required mutability
         target_mut: Mutability,
     },
-    DerefedBorrow {
-        count: usize,
-        required_precedence: i8,
-        msg: &'static str,
-    },
+    DerefedBorrow(DerefedBorrow),
     ExplicitDeref {
-        deref_span: Span,
-        deref_hir_id: HirId,
+        // Span and id of the top-level deref expression if the parent expression is a borrow.
+        deref_span_id: Option<(Span, HirId)>,
+    },
+    ExplicitDerefField {
+        name: Symbol,
     },
     Reborrow {
         deref_span: Span,
@@ -241,13 +249,28 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
 
         match (self.state.take(), kind) {
             (None, kind) => {
-                let parent = get_parent_node(cx.tcx, expr.hir_id);
                 let expr_ty = typeck.expr_ty(expr);
+                let (position, adjustments) = walk_parents(cx, expr);
 
                 match kind {
+                    RefOp::Deref => {
+                        if let Position::FieldAccess(name) = position
+                            && !ty_contains_field(typeck.expr_ty(sub_expr), name)
+                        {
+                            self.state = Some((
+                                State::ExplicitDerefField { name },
+                                StateData { span: expr.span, hir_id: expr.hir_id, position },
+                            ));
+                        } else if position.is_deref_stable() {
+                            self.state = Some((
+                                State::ExplicitDeref { deref_span_id: None },
+                                StateData { span: expr.span, hir_id: expr.hir_id, position },
+                            ));
+                        }
+                    }
                     RefOp::Method(target_mut)
                         if !is_lint_allowed(cx, EXPLICIT_DEREF_METHODS, expr.hir_id)
-                            && is_linted_explicit_deref_position(parent, expr.hir_id, expr.span) =>
+                            && position.lint_explicit_deref() =>
                     {
                         self.state = Some((
                             State::DerefMethod {
@@ -262,11 +285,11 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
                             StateData {
                                 span: expr.span,
                                 hir_id: expr.hir_id,
+                                position
                             },
                         ));
                     },
                     RefOp::AddrOf => {
-                        let (stability, adjustments) = walk_parents(cx, expr);
                         // Find the number of times the borrow is auto-derefed.
                         let mut iter = adjustments.iter();
                         let mut deref_count = 0usize;
@@ -309,47 +332,43 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
                             "this expression creates a reference which is immediately dereferenced by the compiler";
                         let borrow_msg = "this expression borrows a value the compiler would automatically borrow";
 
-                        let (required_refs, required_precedence, msg) = if is_auto_borrow_position(parent, expr.hir_id)
-                        {
-                            (1, PREC_POSTFIX, if deref_count == 1 { borrow_msg } else { deref_msg })
+                        let (required_refs, msg) = if position.can_auto_borrow() {
+                            (1, if deref_count == 1 { borrow_msg } else { deref_msg })
                         } else if let Some(&Adjust::Borrow(AutoBorrow::Ref(_, mutability))) =
                             next_adjust.map(|a| &a.kind)
                         {
-                            if matches!(mutability, AutoBorrowMutability::Mut { .. }) && !stability.is_reborrow_stable()
+                            if matches!(mutability, AutoBorrowMutability::Mut { .. }) && !position.is_reborrow_stable()
                             {
-                                (3, 0, deref_msg)
+                                (3, deref_msg)
                             } else {
-                                (2, 0, deref_msg)
+                                (2, deref_msg)
                             }
                         } else {
-                            (2, 0, deref_msg)
+                            (2, deref_msg)
                         };
 
                         if deref_count >= required_refs {
                             self.state = Some((
-                                State::DerefedBorrow {
+                                State::DerefedBorrow(DerefedBorrow {
                                     // One of the required refs is for the current borrow expression, the remaining ones
                                     // can't be removed without breaking the code. See earlier comment.
                                     count: deref_count - required_refs,
-                                    required_precedence,
                                     msg,
-                                },
-                                StateData {
-                                    span: expr.span,
-                                    hir_id: expr.hir_id,
-                                },
+                                }),
+                                StateData { span: expr.span, hir_id: expr.hir_id, position },
                             ));
-                        } else if stability.is_deref_stable() {
+                        } else if position.is_deref_stable() {
                             self.state = Some((
                                 State::Borrow,
                                 StateData {
                                     span: expr.span,
                                     hir_id: expr.hir_id,
+                                    position
                                 },
                             ));
                         }
                     },
-                    _ => (),
+                    RefOp::Method(..) => (),
                 }
             },
             (
@@ -376,26 +395,47 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
                     data,
                 ));
             },
-            (
-                Some((
-                    State::DerefedBorrow {
-                        count,
-                        required_precedence,
-                        msg,
-                    },
-                    data,
-                )),
-                RefOp::AddrOf,
-            ) if count != 0 => {
+            (Some((State::DerefedBorrow(state), data)), RefOp::AddrOf) if state.count != 0 => {
                 self.state = Some((
-                    State::DerefedBorrow {
-                        count: count - 1,
-                        required_precedence,
-                        msg,
-                    },
+                    State::DerefedBorrow(DerefedBorrow {
+                        count: state.count - 1,
+                        ..state
+                    }),
                     data,
                 ));
             },
+            (Some((State::DerefedBorrow(state), data)), RefOp::AddrOf) => {
+                let position = data.position;
+                report(cx, expr, State::DerefedBorrow(state), data);
+                if position.is_deref_stable() {
+                    self.state = Some((
+                        State::Borrow,
+                        StateData {
+                            span: expr.span,
+                            hir_id: expr.hir_id,
+                            position,
+                        },
+                    ));
+                }
+            },
+            (Some((State::DerefedBorrow(state), data)), RefOp::Deref) => {
+                let position = data.position;
+                report(cx, expr, State::DerefedBorrow(state), data);
+                if let Position::FieldAccess(name) = position
+                    && !ty_contains_field(typeck.expr_ty(sub_expr), name)
+                {
+                    self.state = Some((
+                        State::ExplicitDerefField { name },
+                        StateData { span: expr.span, hir_id: expr.hir_id, position },
+                    ));
+                } else if position.is_deref_stable() {
+                    self.state = Some((
+                        State::ExplicitDeref { deref_span_id: None },
+                        StateData { span: expr.span, hir_id: expr.hir_id, position },
+                    ));
+                }
+            },
+
             (Some((State::Borrow, data)), RefOp::Deref) => {
                 if typeck.expr_ty(sub_expr).is_ref() {
                     self.state = Some((
@@ -408,8 +448,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
                 } else {
                     self.state = Some((
                         State::ExplicitDeref {
-                            deref_span: expr.span,
-                            deref_hir_id: expr.hir_id,
+                            deref_span_id: Some((expr.span, expr.hir_id)),
                         },
                         data,
                     ));
@@ -427,8 +466,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
             ) => {
                 self.state = Some((
                     State::ExplicitDeref {
-                        deref_span,
-                        deref_hir_id,
+                        deref_span_id: Some((deref_span, deref_hir_id)),
                     },
                     data,
                 ));
@@ -436,6 +474,11 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
             (state @ Some((State::ExplicitDeref { .. }, _)), RefOp::Deref) => {
                 self.state = state;
             },
+            (Some((State::ExplicitDerefField { name }, data)), RefOp::Deref)
+                if !ty_contains_field(typeck.expr_ty(sub_expr), name) =>
+            {
+                self.state = Some((State::ExplicitDerefField { name }, data));
+            },
 
             (Some((state, data)), _) => report(cx, expr, state, data),
         }
@@ -555,143 +598,154 @@ fn deref_method_same_type<'tcx>(result_ty: Ty<'tcx>, arg_ty: Ty<'tcx>) -> bool {
     }
 }
 
-// Checks whether the parent node is a suitable context for switching from a deref method to the
-// deref operator.
-fn is_linted_explicit_deref_position(parent: Option<Node<'_>>, child_id: HirId, child_span: Span) -> bool {
-    let parent = match parent {
-        Some(Node::Expr(e)) if e.span.ctxt() == child_span.ctxt() => e,
-        _ => return true,
-    };
-    match parent.kind {
-        // Leave deref calls in the middle of a method chain.
-        // e.g. x.deref().foo()
-        ExprKind::MethodCall(_, [self_arg, ..], _) if self_arg.hir_id == child_id => false,
-
-        // Leave deref calls resulting in a called function
-        // e.g. (x.deref())()
-        ExprKind::Call(func_expr, _) if func_expr.hir_id == child_id => false,
-
-        // Makes an ugly suggestion
-        // e.g. *x.deref() => *&*x
-        ExprKind::Unary(UnOp::Deref, _)
-        // Postfix expressions would require parens
-        | ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar)
-        | ExprKind::Field(..)
-        | ExprKind::Index(..)
-        | ExprKind::Err => false,
-
-        ExprKind::Box(..)
-        | ExprKind::ConstBlock(..)
-        | ExprKind::Array(_)
-        | ExprKind::Call(..)
-        | ExprKind::MethodCall(..)
-        | ExprKind::Tup(..)
-        | ExprKind::Binary(..)
-        | ExprKind::Unary(..)
-        | ExprKind::Lit(..)
-        | ExprKind::Cast(..)
-        | ExprKind::Type(..)
-        | ExprKind::DropTemps(..)
-        | ExprKind::If(..)
-        | ExprKind::Loop(..)
-        | ExprKind::Match(..)
-        | ExprKind::Let(..)
-        | ExprKind::Closure{..}
-        | ExprKind::Block(..)
-        | ExprKind::Assign(..)
-        | ExprKind::AssignOp(..)
-        | ExprKind::Path(..)
-        | ExprKind::AddrOf(..)
-        | ExprKind::Break(..)
-        | ExprKind::Continue(..)
-        | ExprKind::Ret(..)
-        | ExprKind::InlineAsm(..)
-        | ExprKind::Struct(..)
-        | ExprKind::Repeat(..)
-        | ExprKind::Yield(..) => true,
-    }
-}
-
-/// How stable the result of auto-deref is.
+/// The position of an expression relative to it's parent.
 #[derive(Clone, Copy)]
-enum AutoDerefStability {
-    /// Auto-deref will always choose the same type.
+enum Position {
+    MethodReceiver,
+    /// The method is defined on a reference type. e.g. `impl Foo for &T`
+    MethodReceiverRefImpl,
+    Callee,
+    FieldAccess(Symbol),
+    Postfix,
     Deref,
-    /// Auto-deref will always reborrow a reference.
-    Reborrow,
-    /// Auto-deref will not occur, or it may select a different type.
-    None,
+    /// Any other location which will trigger auto-deref to a specific time.
+    DerefStable(i8),
+    /// Any other location which will trigger auto-reborrowing.
+    ReborrowStable(i8),
+    Other(i8),
 }
-impl AutoDerefStability {
+impl Position {
     fn is_deref_stable(self) -> bool {
-        matches!(self, Self::Deref)
+        matches!(self, Self::DerefStable(_))
     }
 
     fn is_reborrow_stable(self) -> bool {
-        matches!(self, Self::Deref | Self::Reborrow)
+        matches!(self, Self::DerefStable(_) | Self::ReborrowStable(_))
+    }
+
+    fn can_auto_borrow(self) -> bool {
+        matches!(self, Self::MethodReceiver | Self::FieldAccess(_) | Self::Callee)
+    }
+
+    fn lint_explicit_deref(self) -> bool {
+        matches!(self, Self::Other(_) | Self::DerefStable(_) | Self::ReborrowStable(_))
+    }
+
+    fn precedence(self) -> i8 {
+        match self {
+            Self::MethodReceiver
+            | Self::MethodReceiverRefImpl
+            | Self::Callee
+            | Self::FieldAccess(_)
+            | Self::Postfix => PREC_POSTFIX,
+            Self::Deref => PREC_PREFIX,
+            Self::DerefStable(p) | Self::ReborrowStable(p) | Self::Other(p) => p,
+        }
     }
 }
 
 /// Walks up the parent expressions attempting to determine both how stable the auto-deref result
 /// is, and which adjustments will be applied to it. Note this will not consider auto-borrow
 /// locations as those follow different rules.
-fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (AutoDerefStability, &'tcx [Adjustment<'tcx>]) {
+#[allow(clippy::too_many_lines)]
+fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, &'tcx [Adjustment<'tcx>]) {
     let mut adjustments = [].as_slice();
-    let stability = walk_to_expr_usage(cx, e, &mut |node, child_id| {
+    let mut precedence = 0i8;
+    let ctxt = e.span.ctxt();
+    let position = walk_to_expr_usage(cx, e, &mut |parent, child_id| {
         // LocalTableInContext returns the wrong lifetime, so go use `expr_adjustments` instead.
         if adjustments.is_empty() && let Node::Expr(e) = cx.tcx.hir().get(child_id) {
             adjustments = cx.typeck_results().expr_adjustments(e);
         }
-        match node {
-            Node::Local(Local { ty: Some(ty), .. }) => Some(binding_ty_auto_deref_stability(ty)),
+        match parent {
+            Node::Local(Local { ty: Some(ty), span, .. }) if span.ctxt() == ctxt => {
+                Some(binding_ty_auto_deref_stability(ty, precedence))
+            },
             Node::Item(&Item {
                 kind: ItemKind::Static(..) | ItemKind::Const(..),
+                def_id,
+                span,
                 ..
             })
             | Node::TraitItem(&TraitItem {
                 kind: TraitItemKind::Const(..),
+                def_id,
+                span,
                 ..
             })
             | Node::ImplItem(&ImplItem {
                 kind: ImplItemKind::Const(..),
+                def_id,
+                span,
                 ..
-            }) => Some(AutoDerefStability::Deref),
+            }) if span.ctxt() == ctxt => {
+                let ty = cx.tcx.type_of(def_id);
+                Some(if ty.is_ref() {
+                    Position::DerefStable(precedence)
+                } else {
+                    Position::Other(precedence)
+                })
+            },
 
             Node::Item(&Item {
                 kind: ItemKind::Fn(..),
                 def_id,
+                span,
                 ..
             })
             | Node::TraitItem(&TraitItem {
                 kind: TraitItemKind::Fn(..),
                 def_id,
+                span,
                 ..
             })
             | Node::ImplItem(&ImplItem {
                 kind: ImplItemKind::Fn(..),
                 def_id,
+                span,
                 ..
-            }) => {
+            }) if span.ctxt() == ctxt => {
                 let output = cx.tcx.fn_sig(def_id.to_def_id()).skip_binder().output();
-                Some(if output.has_placeholders() || output.has_opaque_types() {
-                    AutoDerefStability::Reborrow
+                Some(if !output.is_ref() {
+                    Position::Other(precedence)
+                } else if output.has_placeholders() || output.has_opaque_types() {
+                    Position::ReborrowStable(precedence)
                 } else {
-                    AutoDerefStability::Deref
+                    Position::DerefStable(precedence)
                 })
             },
 
-            Node::Expr(e) => match e.kind {
+            Node::Expr(parent) if parent.span.ctxt() == ctxt => match parent.kind {
                 ExprKind::Ret(_) => {
-                    let output = cx
-                        .tcx
-                        .fn_sig(cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()))
-                        .skip_binder()
-                        .output();
-                    Some(if output.has_placeholders() || output.has_opaque_types() {
-                        AutoDerefStability::Reborrow
-                    } else {
-                        AutoDerefStability::Deref
-                    })
+                    let owner_id = cx.tcx.hir().body_owner(cx.enclosing_body.unwrap());
+                    Some(
+                        if let Node::Expr(Expr {
+                            kind: ExprKind::Closure(&Closure { fn_decl, .. }),
+                            ..
+                        }) = cx.tcx.hir().get(owner_id)
+                        {
+                            match fn_decl.output {
+                                FnRetTy::Return(ty) => binding_ty_auto_deref_stability(ty, precedence),
+                                FnRetTy::DefaultReturn(_) => Position::Other(precedence),
+                            }
+                        } else {
+                            let output = cx
+                                .tcx
+                                .fn_sig(cx.tcx.hir().local_def_id(owner_id))
+                                .skip_binder()
+                                .output();
+                            if !output.is_ref() {
+                                Position::Other(precedence)
+                            } else if output.has_placeholders() || output.has_opaque_types() {
+                                Position::ReborrowStable(precedence)
+                            } else {
+                                Position::DerefStable(precedence)
+                            }
+                        },
+                    )
+                },
+                ExprKind::Call(func, _) if func.hir_id == child_id => {
+                    (child_id == e.hir_id).then_some(Position::Callee)
                 },
                 ExprKind::Call(func, args) => args
                     .iter()
@@ -701,47 +755,80 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (AutoDerefSt
                     .map(|(hir_ty, ty)| match hir_ty {
                         // Type inference for closures can depend on how they're called. Only go by the explicit
                         // types here.
-                        Some(ty) => binding_ty_auto_deref_stability(ty),
-                        None => param_auto_deref_stability(ty.skip_binder()),
+                        Some(ty) => binding_ty_auto_deref_stability(ty, precedence),
+                        None => param_auto_deref_stability(ty.skip_binder(), precedence),
                     }),
-                ExprKind::MethodCall(_, [_, args @ ..], _) => {
-                    let id = cx.typeck_results().type_dependent_def_id(e.hir_id).unwrap();
+                ExprKind::MethodCall(_, args, _) => {
+                    let id = cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap();
                     args.iter().position(|arg| arg.hir_id == child_id).map(|i| {
-                        let arg = cx.tcx.fn_sig(id).skip_binder().inputs()[i + 1];
-                        param_auto_deref_stability(arg)
+                        if i == 0 {
+                            // Check for calls to trait methods where the trait is implemented on a reference.
+                            // Two cases need to be handled:
+                            // * `self` methods on `&T` will never have auto-borrow
+                            // * `&self` methods on `&T` can have auto-borrow, but `&self` methods on `T` will take
+                            //   priority.
+                            if e.hir_id != child_id {
+                                Position::ReborrowStable(precedence)
+                            } else if let Some(trait_id) = cx.tcx.trait_of_item(id)
+                                && let arg_ty = cx.tcx.erase_regions(cx.typeck_results().expr_ty_adjusted(e))
+                                && let ty::Ref(_, sub_ty, _) = *arg_ty.kind()
+                                && let subs = match cx
+                                    .typeck_results()
+                                    .node_substs_opt(parent.hir_id)
+                                    .and_then(|subs| subs.get(1..))
+                                {
+                                    Some(subs) => cx.tcx.mk_substs(subs.iter().copied()),
+                                    None => cx.tcx.mk_substs([].iter()),
+                                } && let impl_ty = if cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref() {
+                                    // Trait methods taking `&self`
+                                    sub_ty
+                                } else {
+                                    // Trait methods taking `self`
+                                    arg_ty
+                                } && impl_ty.is_ref()
+                                && cx.tcx.infer_ctxt().enter(|infcx|
+                                    infcx
+                                        .type_implements_trait(trait_id, impl_ty, subs, cx.param_env)
+                                        .must_apply_modulo_regions()
+                                )
+                            {
+                                Position::MethodReceiverRefImpl
+                            } else {
+                                Position::MethodReceiver
+                            }
+                        } else {
+                            param_auto_deref_stability(cx.tcx.fn_sig(id).skip_binder().inputs()[i], precedence)
+                        }
                     })
                 },
-                ExprKind::MethodCall(..) => Some(AutoDerefStability::Reborrow),
                 ExprKind::Struct(path, fields, _) => {
-                    let variant = variant_of_res(cx, cx.qpath_res(path, e.hir_id));
+                    let variant = variant_of_res(cx, cx.qpath_res(path, parent.hir_id));
                     fields
                         .iter()
                         .find(|f| f.expr.hir_id == child_id)
                         .zip(variant)
                         .and_then(|(field, variant)| variant.fields.iter().find(|f| f.name == field.ident.name))
-                        .map(|field| param_auto_deref_stability(cx.tcx.type_of(field.did)))
+                        .map(|field| param_auto_deref_stability(cx.tcx.type_of(field.did), precedence))
+                },
+                ExprKind::Field(child, name) if child.hir_id == e.hir_id => Some(Position::FieldAccess(name.name)),
+                ExprKind::Unary(UnOp::Deref, child) if child.hir_id == e.hir_id => Some(Position::Deref),
+                ExprKind::Match(child, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar)
+                | ExprKind::Index(child, _)
+                    if child.hir_id == e.hir_id =>
+                {
+                    Some(Position::Postfix)
+                },
+                _ if child_id == e.hir_id => {
+                    precedence = parent.precedence().order();
+                    None
                 },
                 _ => None,
             },
             _ => None,
         }
     })
-    .unwrap_or(AutoDerefStability::None);
-    (stability, adjustments)
-}
-
-/// Checks if the given expression is a position which can auto-borrow.
-fn is_auto_borrow_position(parent: Option<Node<'_>>, child_id: HirId) -> bool {
-    if let Some(Node::Expr(parent)) = parent {
-        match parent.kind {
-            // ExprKind::MethodCall(_, [self_arg, ..], _) => self_arg.hir_id == child_id,
-            ExprKind::Field(..) => true,
-            ExprKind::Call(f, _) => f.hir_id == child_id,
-            _ => false,
-        }
-    } else {
-        false
-    }
+    .unwrap_or(Position::Other(precedence));
+    (position, adjustments)
 }
 
 // Checks the stability of auto-deref when assigned to a binding with the given explicit type.
@@ -753,9 +840,9 @@ fn is_auto_borrow_position(parent: Option<Node<'_>>, child_id: HirId) -> bool {
 //
 // Here `y1` and `y2` would resolve to different types, so the type `&Box<_>` is not stable when
 // switching to auto-dereferencing.
-fn binding_ty_auto_deref_stability(ty: &hir::Ty<'_>) -> AutoDerefStability {
+fn binding_ty_auto_deref_stability(ty: &hir::Ty<'_>, precedence: i8) -> Position {
     let TyKind::Rptr(_, ty) = &ty.kind else {
-        return AutoDerefStability::None;
+        return Position::Other(precedence);
     };
     let mut ty = ty;
 
@@ -781,9 +868,9 @@ fn binding_ty_auto_deref_stability(ty: &hir::Ty<'_>) -> AutoDerefStability {
                         _ => false,
                     })
                 {
-                    AutoDerefStability::Reborrow
+                    Position::ReborrowStable(precedence)
                 } else {
-                    AutoDerefStability::Deref
+                    Position::DerefStable(precedence)
                 }
             },
             TyKind::Slice(_)
@@ -793,8 +880,11 @@ fn binding_ty_auto_deref_stability(ty: &hir::Ty<'_>) -> AutoDerefStability {
             | TyKind::Tup(_)
             | TyKind::Ptr(_)
             | TyKind::TraitObject(..)
-            | TyKind::Path(_) => AutoDerefStability::Deref,
-            TyKind::OpaqueDef(..) | TyKind::Infer | TyKind::Typeof(..) | TyKind::Err => AutoDerefStability::Reborrow,
+            | TyKind::Path(_) => Position::DerefStable(precedence),
+            TyKind::OpaqueDef(..)
+            | TyKind::Infer
+            | TyKind::Typeof(..)
+            | TyKind::Err => Position::ReborrowStable(precedence),
         };
     }
 }
@@ -802,44 +892,38 @@ fn binding_ty_auto_deref_stability(ty: &hir::Ty<'_>) -> AutoDerefStability {
 // Checks whether a type is inferred at some point.
 // e.g. `_`, `Box<_>`, `[_]`
 fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool {
-    match &ty.kind {
-        TyKind::Slice(ty) | TyKind::Array(ty, _) => ty_contains_infer(ty),
-        TyKind::Ptr(ty) | TyKind::Rptr(_, ty) => ty_contains_infer(ty.ty),
-        TyKind::Tup(tys) => tys.iter().any(ty_contains_infer),
-        TyKind::BareFn(ty) => {
-            if ty.decl.inputs.iter().any(ty_contains_infer) {
-                return true;
-            }
-            if let FnRetTy::Return(ty) = &ty.decl.output {
-                ty_contains_infer(ty)
+    struct V(bool);
+    impl Visitor<'_> for V {
+        fn visit_ty(&mut self, ty: &hir::Ty<'_>) {
+            if self.0
+                || matches!(
+                    ty.kind,
+                    TyKind::OpaqueDef(..) | TyKind::Infer | TyKind::Typeof(_) | TyKind::Err
+                )
+            {
+                self.0 = true;
             } else {
-                false
+                walk_ty(self, ty);
             }
-        },
-        &TyKind::Path(
-            QPath::TypeRelative(_, path)
-            | QPath::Resolved(
-                _,
-                Path {
-                    segments: [.., path], ..
-                },
-            ),
-        ) => path.args.map_or(false, |args| {
-            args.args.iter().any(|arg| match arg {
-                GenericArg::Infer(_) => true,
-                GenericArg::Type(ty) => ty_contains_infer(ty),
-                _ => false,
-            })
-        }),
-        TyKind::Path(_) | TyKind::OpaqueDef(..) | TyKind::Infer | TyKind::Typeof(_) | TyKind::Err => true,
-        TyKind::Never | TyKind::TraitObject(..) => false,
+        }
+
+        fn visit_generic_arg(&mut self, arg: &GenericArg<'_>) {
+            if self.0 || matches!(arg, GenericArg::Infer(_)) {
+                self.0 = true;
+            } else if let GenericArg::Type(ty) = arg {
+                self.visit_ty(ty);
+            }
+        }
     }
+    let mut v = V(false);
+    v.visit_ty(ty);
+    v.0
 }
 
 // Checks whether a type is stable when switching to auto dereferencing,
-fn param_auto_deref_stability(ty: Ty<'_>) -> AutoDerefStability {
+fn param_auto_deref_stability(ty: Ty<'_>, precedence: i8) -> Position {
     let ty::Ref(_, mut ty, _) = *ty.kind() else {
-        return AutoDerefStability::None;
+        return Position::Other(precedence);
     };
 
     loop {
@@ -848,7 +932,18 @@ fn param_auto_deref_stability(ty: Ty<'_>) -> AutoDerefStability {
                 ty = ref_ty;
                 continue;
             },
-            ty::Bool
+            ty::Infer(_)
+            | ty::Error(_)
+            | ty::Param(_)
+            | ty::Bound(..)
+            | ty::Opaque(..)
+            | ty::Placeholder(_)
+            | ty::Dynamic(..) => Position::ReborrowStable(precedence),
+            ty::Adt(..) if ty.has_placeholders() || ty.has_param_types_or_consts() => {
+                Position::ReborrowStable(precedence)
+            },
+            ty::Adt(..)
+            | ty::Bool
             | ty::Char
             | ty::Int(_)
             | ty::Uint(_)
@@ -865,21 +960,20 @@ fn param_auto_deref_stability(ty: Ty<'_>) -> AutoDerefStability {
             | ty::GeneratorWitness(..)
             | ty::Never
             | ty::Tuple(_)
-            | ty::Projection(_) => AutoDerefStability::Deref,
-            ty::Infer(_)
-            | ty::Error(_)
-            | ty::Param(_)
-            | ty::Bound(..)
-            | ty::Opaque(..)
-            | ty::Placeholder(_)
-            | ty::Dynamic(..) => AutoDerefStability::Reborrow,
-            ty::Adt(..) if ty.has_placeholders() || ty.has_param_types_or_consts() => AutoDerefStability::Reborrow,
-            ty::Adt(..) => AutoDerefStability::Deref,
+            | ty::Projection(_) => Position::DerefStable(precedence),
         };
     }
 }
 
-#[expect(clippy::needless_pass_by_value)]
+fn ty_contains_field(ty: Ty<'_>, name: Symbol) -> bool {
+    if let ty::Adt(adt, _) = *ty.kind() {
+        adt.is_struct() && adt.all_fields().any(|f| f.name == name)
+    } else {
+        false
+    }
+}
+
+#[expect(clippy::needless_pass_by_value, clippy::too_many_lines)]
 fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data: StateData) {
     match state {
         State::DerefMethod {
@@ -930,15 +1024,14 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
                 app,
             );
         },
-        State::DerefedBorrow {
-            required_precedence,
-            msg,
-            ..
-        } => {
+        State::DerefedBorrow(state) => {
             let mut app = Applicability::MachineApplicable;
-            let snip = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app).0;
-            span_lint_hir_and_then(cx, NEEDLESS_BORROW, data.hir_id, data.span, msg, |diag| {
-                let sugg = if required_precedence > expr.precedence().order() && !has_enclosing_paren(&snip) {
+            let (snip, snip_is_macro) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app);
+            span_lint_hir_and_then(cx, NEEDLESS_BORROW, data.hir_id, data.span, state.msg, |diag| {
+                let sugg = if !snip_is_macro
+                    && expr.precedence().order() < data.position.precedence()
+                    && !has_enclosing_paren(&snip)
+                {
                     format!("({})", snip)
                 } else {
                     snip.into()
@@ -946,14 +1039,13 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
                 diag.span_suggestion(data.span, "change this to", sugg, app);
             });
         },
-        State::ExplicitDeref {
-            deref_span,
-            deref_hir_id,
-        } => {
-            let (span, hir_id) = if cx.typeck_results().expr_ty(expr).is_ref() {
-                (data.span, data.hir_id)
+        State::ExplicitDeref { deref_span_id } => {
+            let (span, hir_id, precedence) = if let Some((span, hir_id)) = deref_span_id
+                && !cx.typeck_results().expr_ty(expr).is_ref()
+            {
+                (span, hir_id, PREC_PREFIX)
             } else {
-                (deref_span, deref_hir_id)
+                (data.span, data.hir_id, data.position.precedence())
             };
             span_lint_hir_and_then(
                 cx,
@@ -963,8 +1055,28 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
                 "deref which would be done by auto-deref",
                 |diag| {
                     let mut app = Applicability::MachineApplicable;
-                    let snip = snippet_with_context(cx, expr.span, span.ctxt(), "..", &mut app).0;
-                    diag.span_suggestion(span, "try this", snip.into_owned(), app);
+                    let (snip, snip_is_macro) = snippet_with_context(cx, expr.span, span.ctxt(), "..", &mut app);
+                    let sugg =
+                        if !snip_is_macro && expr.precedence().order() < precedence && !has_enclosing_paren(&snip) {
+                            format!("({})", snip)
+                        } else {
+                            snip.into()
+                        };
+                    diag.span_suggestion(span, "try this", sugg, app);
+                },
+            );
+        },
+        State::ExplicitDerefField { .. } => {
+            span_lint_hir_and_then(
+                cx,
+                EXPLICIT_AUTO_DEREF,
+                data.hir_id,
+                data.span,
+                "deref which would be done by auto-deref",
+                |diag| {
+                    let mut app = Applicability::MachineApplicable;
+                    let snip = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app).0;
+                    diag.span_suggestion(data.span, "try this", snip.into_owned(), app);
                 },
             );
         },