about summary refs log tree commit diff
diff options
context:
space:
mode:
authorsjwang05 <63834813+sjwang05@users.noreply.github.com>2023-12-16 17:34:52 -0800
committersjwang05 <63834813+sjwang05@users.noreply.github.com>2023-12-16 19:56:50 -0800
commit2618e0f805bda52ef07704872d9751ee22e40eda (patch)
tree8fde21860916d781253eeceb4f577ddf0e6e9192
parentde686cbc65478db53e3d51c52497685e852cc092 (diff)
downloadrust-2618e0f805bda52ef07704872d9751ee22e40eda.tar.gz
rust-2618e0f805bda52ef07704872d9751ee22e40eda.zip
Provide better suggestions for T == &T and &T == T
-rw-r--r--Cargo.lock1
-rw-r--r--compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs2
-rw-r--r--compiler/rustc_hir_typeck/src/op.rs23
-rw-r--r--compiler/rustc_middle/src/traits/mod.rs19
-rw-r--r--compiler/rustc_trait_selection/Cargo.toml1
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs365
-rw-r--r--tests/ui/binop/binary-op-suggest-deref.fixed8
-rw-r--r--tests/ui/binop/binary-op-suggest-deref.rs73
-rw-r--r--tests/ui/binop/binary-op-suggest-deref.stderr296
-rw-r--r--tests/ui/dst/issue-113447.fixed25
-rw-r--r--tests/ui/dst/issue-113447.rs2
-rw-r--r--tests/ui/dst/issue-113447.stderr19
-rw-r--r--tests/ui/partialeq_help.stderr8
13 files changed, 668 insertions, 174 deletions
diff --git a/Cargo.lock b/Cargo.lock
index a5bdc14fe99..9e28c1158a3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4580,6 +4580,7 @@ checksum = "8ba09476327c4b70ccefb6180f046ef588c26a24cf5d269a9feba316eb4f029f"
 name = "rustc_trait_selection"
 version = "0.0.0"
 dependencies = [
+ "itertools",
  "rustc_ast",
  "rustc_attr",
  "rustc_data_structures",
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
index 28f377083f6..64736bb1cae 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
@@ -2441,7 +2441,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                         }
                     };
 
-                    // Suggest dereferencing the lhs for expressions such as `&T == T`
+                    // Suggest dereferencing the lhs for expressions such as `&T <= T`
                     if let Some(hir::Node::Expr(hir::Expr {
                         kind: hir::ExprKind::Binary(_, lhs, ..),
                         ..
diff --git a/compiler/rustc_hir_typeck/src/op.rs b/compiler/rustc_hir_typeck/src/op.rs
index 59b4b0032f8..68156ca563c 100644
--- a/compiler/rustc_hir_typeck/src/op.rs
+++ b/compiler/rustc_hir_typeck/src/op.rs
@@ -47,7 +47,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             if let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty) {
                 if self
                     .lookup_op_method(
-                        lhs_deref_ty,
+                        (lhs, lhs_deref_ty),
                         Some((rhs, rhs_ty)),
                         Op::Binary(op, IsAssign::Yes),
                         expected,
@@ -58,7 +58,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     // emitted a better suggestion during error handling in check_overloaded_binop.
                     if self
                         .lookup_op_method(
-                            lhs_ty,
+                            (lhs, lhs_ty),
                             Some((rhs, rhs_ty)),
                             Op::Binary(op, IsAssign::Yes),
                             expected,
@@ -246,7 +246,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         });
 
         let result = self.lookup_op_method(
-            lhs_ty,
+            (lhs_expr, lhs_ty),
             Some((rhs_expr, rhs_ty_var)),
             Op::Binary(op, is_assign),
             expected,
@@ -391,7 +391,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     |err: &mut DiagnosticBuilder<'_, _>, lhs_deref_ty: Ty<'tcx>| {
                         if self
                             .lookup_op_method(
-                                lhs_deref_ty,
+                                (lhs_expr, lhs_deref_ty),
                                 Some((rhs_expr, rhs_ty)),
                                 Op::Binary(op, is_assign),
                                 expected,
@@ -424,7 +424,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                      rhs_new_mutbl: Option<ast::Mutability>| {
                         if self
                             .lookup_op_method(
-                                lhs_adjusted_ty,
+                                (lhs_expr, lhs_adjusted_ty),
                                 Some((rhs_expr, rhs_adjusted_ty)),
                                 Op::Binary(op, is_assign),
                                 expected,
@@ -479,7 +479,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
 
                 let is_compatible_after_call = |lhs_ty, rhs_ty| {
                     self.lookup_op_method(
-                        lhs_ty,
+                        (lhs_expr, lhs_ty),
                         Some((rhs_expr, rhs_ty)),
                         Op::Binary(op, is_assign),
                         expected,
@@ -578,7 +578,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                         // suggestion for the user.
                         let errors = self
                             .lookup_op_method(
-                                lhs_ty,
+                                (lhs_expr, lhs_ty),
                                 Some((rhs_expr, rhs_ty)),
                                 Op::Binary(op, is_assign),
                                 expected,
@@ -779,7 +779,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         expected: Expectation<'tcx>,
     ) -> Ty<'tcx> {
         assert!(op.is_by_value());
-        match self.lookup_op_method(operand_ty, None, Op::Unary(op, ex.span), expected) {
+        match self.lookup_op_method((ex, operand_ty), None, Op::Unary(op, ex.span), expected) {
             Ok(method) => {
                 self.write_method_call_and_enforce_effects(ex.hir_id, ex.span, method);
                 method.sig.output()
@@ -865,7 +865,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
 
     fn lookup_op_method(
         &self,
-        lhs_ty: Ty<'tcx>,
+        (lhs_expr, lhs_ty): (&'tcx hir::Expr<'tcx>, Ty<'tcx>),
         opt_rhs: Option<(&'tcx hir::Expr<'tcx>, Ty<'tcx>)>,
         op: Op,
         expected: Expectation<'tcx>,
@@ -910,8 +910,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         let cause = self.cause(
             span,
             traits::BinOp {
+                lhs_hir_id: lhs_expr.hir_id,
+                rhs_hir_id: opt_rhs_expr.map(|expr| expr.hir_id),
                 rhs_span: opt_rhs_expr.map(|expr| expr.span),
-                is_lit: opt_rhs_expr.is_some_and(|expr| matches!(expr.kind, hir::ExprKind::Lit(_))),
+                rhs_is_lit: opt_rhs_expr
+                    .is_some_and(|expr| matches!(expr.kind, hir::ExprKind::Lit(_))),
                 output_ty: expected.only_has_type(self),
             },
         );
diff --git a/compiler/rustc_middle/src/traits/mod.rs b/compiler/rustc_middle/src/traits/mod.rs
index 5d0187a8598..178c9581a31 100644
--- a/compiler/rustc_middle/src/traits/mod.rs
+++ b/compiler/rustc_middle/src/traits/mod.rs
@@ -429,8 +429,10 @@ pub enum ObligationCauseCode<'tcx> {
     MatchImpl(ObligationCause<'tcx>, DefId),
 
     BinOp {
+        lhs_hir_id: hir::HirId,
+        rhs_hir_id: Option<hir::HirId>,
         rhs_span: Option<Span>,
-        is_lit: bool,
+        rhs_is_lit: bool,
         output_ty: Option<Ty<'tcx>>,
     },
 
@@ -510,6 +512,21 @@ impl<'tcx> ObligationCauseCode<'tcx> {
         base_cause
     }
 
+    /// Returns the base obligation and the base trait predicate, if any, ignoring
+    /// derived obligations.
+    pub fn peel_derives_with_predicate(&self) -> (&Self, Option<ty::PolyTraitPredicate<'tcx>>) {
+        let mut base_cause = self;
+        let mut base_trait_pred = None;
+        while let Some((parent_code, parent_pred)) = base_cause.parent() {
+            base_cause = parent_code;
+            if let Some(parent_pred) = parent_pred {
+                base_trait_pred = Some(parent_pred);
+            }
+        }
+
+        (base_cause, base_trait_pred)
+    }
+
     pub fn parent(&self) -> Option<(&Self, Option<ty::PolyTraitPredicate<'tcx>>)> {
         match self {
             FunctionArgumentObligation { parent_code, .. } => Some((parent_code, None)),
diff --git a/compiler/rustc_trait_selection/Cargo.toml b/compiler/rustc_trait_selection/Cargo.toml
index 29c0d8b5ff1..1883099d345 100644
--- a/compiler/rustc_trait_selection/Cargo.toml
+++ b/compiler/rustc_trait_selection/Cargo.toml
@@ -5,6 +5,7 @@ edition = "2021"
 
 [dependencies]
 # tidy-alphabetical-start
+itertools = "0.11.0"
 rustc_ast = { path = "../rustc_ast" }
 rustc_attr = { path = "../rustc_attr" }
 rustc_data_structures = { path = "../rustc_data_structures" }
diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
index 9073cd6ac47..7d2feff350a 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
@@ -47,6 +47,9 @@ use crate::traits::error_reporting::type_err_ctxt_ext::InferCtxtPrivExt;
 use crate::traits::query::evaluate_obligation::InferCtxtExt as _;
 use rustc_middle::ty::print::{with_forced_trimmed_paths, with_no_trimmed_paths};
 
+use itertools::EitherOrBoth;
+use itertools::Itertools;
+
 #[derive(Debug)]
 pub enum CoroutineInteriorOrUpvar {
     // span of interior type
@@ -723,133 +726,276 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
         err: &mut Diagnostic,
         trait_pred: ty::PolyTraitPredicate<'tcx>,
     ) -> bool {
-        // It only make sense when suggesting dereferences for arguments
-        let ObligationCauseCode::FunctionArgumentObligation { arg_hir_id, call_hir_id, .. } =
-            obligation.cause.code()
-        else {
-            return false;
-        };
-        let Some(typeck_results) = &self.typeck_results else {
-            return false;
-        };
-        let hir::Node::Expr(expr) = self.tcx.hir_node(*arg_hir_id) else {
-            return false;
-        };
-        let Some(arg_ty) = typeck_results.expr_ty_adjusted_opt(expr) else {
-            return false;
-        };
-
-        let span = obligation.cause.span;
-        let mut real_trait_pred = trait_pred;
         let mut code = obligation.cause.code();
-        while let Some((parent_code, parent_trait_pred)) = code.parent() {
-            code = parent_code;
-            if let Some(parent_trait_pred) = parent_trait_pred {
-                real_trait_pred = parent_trait_pred;
-            }
+        if let ObligationCauseCode::FunctionArgumentObligation { arg_hir_id, call_hir_id, .. } =
+            code
+            && let Some(typeck_results) = &self.typeck_results
+            && let hir::Node::Expr(expr) = self.tcx.hir_node(*arg_hir_id)
+            && let Some(arg_ty) = typeck_results.expr_ty_adjusted_opt(expr)
+        {
+            // Suggest dereferencing the argument to a function/method call if possible
 
-            // We `instantiate_bound_regions_with_erased` here because `make_subregion` does not handle
-            // `ReBound`, and we don't particularly care about the regions.
-            let real_ty = self.tcx.instantiate_bound_regions_with_erased(real_trait_pred.self_ty());
-            if !self.can_eq(obligation.param_env, real_ty, arg_ty) {
-                continue;
-            }
+            let mut real_trait_pred = trait_pred;
+            while let Some((parent_code, parent_trait_pred)) = code.parent() {
+                code = parent_code;
+                if let Some(parent_trait_pred) = parent_trait_pred {
+                    real_trait_pred = parent_trait_pred;
+                }
 
-            if let ty::Ref(region, base_ty, mutbl) = *real_ty.kind() {
-                let autoderef = (self.autoderef_steps)(base_ty);
-                if let Some(steps) =
-                    autoderef.into_iter().enumerate().find_map(|(steps, (ty, obligations))| {
-                        // Re-add the `&`
-                        let ty = Ty::new_ref(self.tcx, region, TypeAndMut { ty, mutbl });
+                // We `instantiate_bound_regions_with_erased` here because `make_subregion` does not handle
+                // `ReBound`, and we don't particularly care about the regions.
+                let real_ty =
+                    self.tcx.instantiate_bound_regions_with_erased(real_trait_pred.self_ty());
 
+                if self.can_eq(obligation.param_env, real_ty, arg_ty)
+                    && let ty::Ref(region, base_ty, mutbl) = *real_ty.kind()
+                {
+                    let autoderef = (self.autoderef_steps)(base_ty);
+                    if let Some(steps) =
+                        autoderef.into_iter().enumerate().find_map(|(steps, (ty, obligations))| {
+                            // Re-add the `&`
+                            let ty = Ty::new_ref(self.tcx, region, TypeAndMut { ty, mutbl });
+
+                            // Remapping bound vars here
+                            let real_trait_pred_and_ty = real_trait_pred
+                                .map_bound(|inner_trait_pred| (inner_trait_pred, ty));
+                            let obligation = self.mk_trait_obligation_with_new_self_ty(
+                                obligation.param_env,
+                                real_trait_pred_and_ty,
+                            );
+                            let may_hold = obligations
+                                .iter()
+                                .chain([&obligation])
+                                .all(|obligation| self.predicate_may_hold(obligation))
+                                .then_some(steps);
+
+                            may_hold
+                        })
+                    {
+                        if steps > 0 {
+                            // Don't care about `&mut` because `DerefMut` is used less
+                            // often and user will not expect that an autoderef happens.
+                            if let Some(hir::Node::Expr(hir::Expr {
+                                kind:
+                                    hir::ExprKind::AddrOf(
+                                        hir::BorrowKind::Ref,
+                                        hir::Mutability::Not,
+                                        expr,
+                                    ),
+                                ..
+                            })) = self.tcx.opt_hir_node(*arg_hir_id)
+                            {
+                                let derefs = "*".repeat(steps);
+                                err.span_suggestion_verbose(
+                                    expr.span.shrink_to_lo(),
+                                    "consider dereferencing here",
+                                    derefs,
+                                    Applicability::MachineApplicable,
+                                );
+                                return true;
+                            }
+                        }
+                    } else if real_trait_pred != trait_pred {
+                        // This branch addresses #87437.
+
+                        let span = obligation.cause.span;
                         // Remapping bound vars here
-                        let real_trait_pred_and_ty =
-                            real_trait_pred.map_bound(|inner_trait_pred| (inner_trait_pred, ty));
+                        let real_trait_pred_and_base_ty = real_trait_pred
+                            .map_bound(|inner_trait_pred| (inner_trait_pred, base_ty));
                         let obligation = self.mk_trait_obligation_with_new_self_ty(
                             obligation.param_env,
-                            real_trait_pred_and_ty,
+                            real_trait_pred_and_base_ty,
                         );
-                        let may_hold = obligations
-                            .iter()
-                            .chain([&obligation])
-                            .all(|obligation| self.predicate_may_hold(obligation))
-                            .then_some(steps);
-
-                        may_hold
-                    })
-                {
-                    if steps > 0 {
-                        // Don't care about `&mut` because `DerefMut` is used less
-                        // often and user will not expect autoderef happens.
-                        if let Some(hir::Node::Expr(hir::Expr {
-                            kind:
-                                hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, expr),
-                            ..
-                        })) = self.tcx.opt_hir_node(*arg_hir_id)
+                        let sized_obligation = Obligation::new(
+                            self.tcx,
+                            obligation.cause.clone(),
+                            obligation.param_env,
+                            ty::TraitRef::from_lang_item(
+                                self.tcx,
+                                hir::LangItem::Sized,
+                                obligation.cause.span,
+                                [base_ty],
+                            ),
+                        );
+                        if self.predicate_may_hold(&obligation)
+                            && self.predicate_must_hold_modulo_regions(&sized_obligation)
                         {
-                            let derefs = "*".repeat(steps);
-                            err.span_suggestion_verbose(
-                                expr.span.shrink_to_lo(),
-                                "consider dereferencing here",
-                                derefs,
-                                Applicability::MachineApplicable,
+                            let call_node = self.tcx.hir_node(*call_hir_id);
+                            let msg = "consider dereferencing here";
+                            let is_receiver = matches!(
+                                call_node,
+                                Node::Expr(hir::Expr {
+                                    kind: hir::ExprKind::MethodCall(_, receiver_expr, ..),
+                                    ..
+                                })
+                                if receiver_expr.hir_id == *arg_hir_id
                             );
+                            if is_receiver {
+                                err.multipart_suggestion_verbose(
+                                    msg,
+                                    vec![
+                                        (span.shrink_to_lo(), "(*".to_string()),
+                                        (span.shrink_to_hi(), ")".to_string()),
+                                    ],
+                                    Applicability::MachineApplicable,
+                                )
+                            } else {
+                                err.span_suggestion_verbose(
+                                    span.shrink_to_lo(),
+                                    msg,
+                                    '*',
+                                    Applicability::MachineApplicable,
+                                )
+                            };
                             return true;
                         }
                     }
-                } else if real_trait_pred != trait_pred {
-                    // This branch addresses #87437.
-
-                    // Remapping bound vars here
-                    let real_trait_pred_and_base_ty =
-                        real_trait_pred.map_bound(|inner_trait_pred| (inner_trait_pred, base_ty));
+                }
+            }
+        } else if let (
+            ObligationCauseCode::BinOp { lhs_hir_id, rhs_hir_id: Some(rhs_hir_id), .. },
+            predicate,
+        ) = code.peel_derives_with_predicate()
+            && let Some(typeck_results) = &self.typeck_results
+            && let hir::Node::Expr(lhs) = self.tcx.hir_node(*lhs_hir_id)
+            && let hir::Node::Expr(rhs) = self.tcx.hir_node(*rhs_hir_id)
+            && let Some(rhs_ty) = typeck_results.expr_ty_opt(rhs)
+        {
+            // Suggest dereferencing the LHS, RHS, or both terms of a binop if possible
+
+            let trait_pred = predicate.unwrap_or(trait_pred);
+            let lhs_ty = self.tcx.instantiate_bound_regions_with_erased(trait_pred.self_ty());
+            let lhs_autoderef = (self.autoderef_steps)(lhs_ty);
+            let rhs_autoderef = (self.autoderef_steps)(rhs_ty);
+            let first_lhs = lhs_autoderef.first().unwrap().clone();
+            let first_rhs = rhs_autoderef.first().unwrap().clone();
+            let mut autoderefs = lhs_autoderef
+                .into_iter()
+                .enumerate()
+                .rev()
+                .zip_longest(rhs_autoderef.into_iter().enumerate().rev())
+                .map(|t| match t {
+                    EitherOrBoth::Both(a, b) => (a, b),
+                    EitherOrBoth::Left(a) => (a, (0, first_rhs.clone())),
+                    EitherOrBoth::Right(b) => ((0, first_lhs.clone()), b),
+                })
+                .rev();
+            if let Some((lsteps, rsteps)) =
+                autoderefs.find_map(|((lsteps, (l_ty, _)), (rsteps, (r_ty, _)))| {
+                    // Create a new predicate with the dereferenced LHS and RHS
+                    // We simultaneously dereference both sides rather than doing them
+                    // one at a time to account for cases such as &Box<T> == &&T
+                    let trait_pred_and_ty = trait_pred.map_bound(|inner| {
+                        (
+                            ty::TraitPredicate {
+                                trait_ref: ty::TraitRef::new(
+                                    self.tcx,
+                                    inner.trait_ref.def_id,
+                                    self.tcx.mk_args(
+                                        &[&[l_ty.into(), r_ty.into()], &inner.trait_ref.args[2..]]
+                                            .concat(),
+                                    ),
+                                ),
+                                ..inner
+                            },
+                            l_ty,
+                        )
+                    });
                     let obligation = self.mk_trait_obligation_with_new_self_ty(
                         obligation.param_env,
-                        real_trait_pred_and_base_ty,
-                    );
-                    let sized_obligation = Obligation::new(
-                        self.tcx,
-                        obligation.cause.clone(),
-                        obligation.param_env,
-                        ty::TraitRef::from_lang_item(
-                            self.tcx,
-                            hir::LangItem::Sized,
-                            obligation.cause.span,
-                            [base_ty],
-                        ),
+                        trait_pred_and_ty,
                     );
-                    if self.predicate_may_hold(&obligation)
-                        && self.predicate_must_hold_modulo_regions(&sized_obligation)
+                    self.predicate_may_hold(&obligation).then_some(match (lsteps, rsteps) {
+                        (_, 0) => (Some(lsteps), None),
+                        (0, _) => (None, Some(rsteps)),
+                        _ => (Some(lsteps), Some(rsteps)),
+                    })
+                })
+            {
+                let make_sugg = |mut expr: &Expr<'_>, mut steps| {
+                    let mut prefix_span = expr.span.shrink_to_lo();
+                    let mut msg = "consider dereferencing here";
+                    if let hir::ExprKind::AddrOf(_, _, inner) = expr.kind {
+                        msg = "consider removing the borrow and dereferencing instead";
+                        if let hir::ExprKind::AddrOf(..) = inner.kind {
+                            msg = "consider removing the borrows and dereferencing instead";
+                        }
+                    }
+                    while let hir::ExprKind::AddrOf(_, _, inner) = expr.kind
+                        && steps > 0
                     {
-                        let call_node = self.tcx.hir_node(*call_hir_id);
-                        let msg = "consider dereferencing here";
-                        let is_receiver = matches!(
-                            call_node,
-                            Node::Expr(hir::Expr {
-                                kind: hir::ExprKind::MethodCall(_, receiver_expr, ..),
-                                ..
-                            })
-                            if receiver_expr.hir_id == *arg_hir_id
+                        prefix_span = prefix_span.with_hi(inner.span.lo());
+                        expr = inner;
+                        steps -= 1;
+                    }
+                    // Empty suggestions with empty spans ICE with debug assertions
+                    if steps == 0 {
+                        return (
+                            msg.trim_end_matches(" and dereferencing instead"),
+                            vec![(prefix_span, String::new())],
                         );
-                        if is_receiver {
-                            err.multipart_suggestion_verbose(
-                                msg,
-                                vec![
-                                    (span.shrink_to_lo(), "(*".to_string()),
-                                    (span.shrink_to_hi(), ")".to_string()),
-                                ],
-                                Applicability::MachineApplicable,
-                            )
-                        } else {
-                            err.span_suggestion_verbose(
-                                span.shrink_to_lo(),
-                                msg,
-                                '*',
-                                Applicability::MachineApplicable,
-                            )
+                    }
+                    let derefs = "*".repeat(steps);
+                    let needs_parens = steps > 0
+                        && match expr.kind {
+                            hir::ExprKind::Cast(_, _) | hir::ExprKind::Binary(_, _, _) => true,
+                            _ if is_range_literal(expr) => true,
+                            _ => false,
                         };
-                        return true;
+                    let mut suggestion = if needs_parens {
+                        vec![
+                            (
+                                expr.span.with_lo(prefix_span.hi()).shrink_to_lo(),
+                                format!("{derefs}("),
+                            ),
+                            (expr.span.shrink_to_hi(), ")".to_string()),
+                        ]
+                    } else {
+                        vec![(
+                            expr.span.with_lo(prefix_span.hi()).shrink_to_lo(),
+                            format!("{derefs}"),
+                        )]
+                    };
+                    // Empty suggestions with empty spans ICE with debug assertions
+                    if !prefix_span.is_empty() {
+                        suggestion.push((prefix_span, String::new()));
                     }
+                    (msg, suggestion)
+                };
+
+                if let Some(lsteps) = lsteps
+                    && let Some(rsteps) = rsteps
+                    && lsteps > 0
+                    && rsteps > 0
+                {
+                    let mut suggestion = make_sugg(lhs, lsteps).1;
+                    suggestion.append(&mut make_sugg(rhs, rsteps).1);
+                    err.multipart_suggestion_verbose(
+                        "consider dereferencing both sides of the expression",
+                        suggestion,
+                        Applicability::MachineApplicable,
+                    );
+                    return true;
+                } else if let Some(lsteps) = lsteps
+                    && lsteps > 0
+                {
+                    let (msg, suggestion) = make_sugg(lhs, lsteps);
+                    err.multipart_suggestion_verbose(
+                        msg,
+                        suggestion,
+                        Applicability::MachineApplicable,
+                    );
+                    return true;
+                } else if let Some(rsteps) = rsteps
+                    && rsteps > 0
+                {
+                    let (msg, suggestion) = make_sugg(rhs, rsteps);
+                    err.multipart_suggestion_verbose(
+                        msg,
+                        suggestion,
+                        Applicability::MachineApplicable,
+                    );
+                    return true;
                 }
             }
         }
@@ -3591,7 +3737,9 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
         trait_ref: &ty::PolyTraitRef<'tcx>,
     ) {
         let rhs_span = match obligation.cause.code() {
-            ObligationCauseCode::BinOp { rhs_span: Some(span), is_lit, .. } if *is_lit => span,
+            ObligationCauseCode::BinOp { rhs_span: Some(span), rhs_is_lit, .. } if *rhs_is_lit => {
+                span
+            }
             _ => return,
         };
         if let ty::Float(_) = trait_ref.skip_binder().self_ty().kind()
@@ -3694,6 +3842,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
             );
         }
     }
+
     fn note_function_argument_obligation(
         &self,
         body_id: LocalDefId,
diff --git a/tests/ui/binop/binary-op-suggest-deref.fixed b/tests/ui/binop/binary-op-suggest-deref.fixed
deleted file mode 100644
index 1ff3599137b..00000000000
--- a/tests/ui/binop/binary-op-suggest-deref.fixed
+++ /dev/null
@@ -1,8 +0,0 @@
-// Issue #52544
-// run-rustfix
-
-fn main() {
-    let i: &i64 = &1;
-    if *i < 0 {}
-    //~^ ERROR mismatched types [E0308]
-}
diff --git a/tests/ui/binop/binary-op-suggest-deref.rs b/tests/ui/binop/binary-op-suggest-deref.rs
index 12505a9ac27..57f24a4c28e 100644
--- a/tests/ui/binop/binary-op-suggest-deref.rs
+++ b/tests/ui/binop/binary-op-suggest-deref.rs
@@ -1,8 +1,75 @@
-// Issue #52544
-// run-rustfix
+#![allow(dead_code)]
 
-fn main() {
+fn foo() {
+    // Issue #52544
     let i: &i64 = &1;
     if i < 0 {}
     //~^ ERROR mismatched types [E0308]
 }
+
+fn bar() {
+    // Issue #40660
+    let foo = &&0;
+
+    // Dereference LHS
+    _ = foo == 0;
+    //~^ERROR can't compare `&&{integer}` with `{integer}` [E0277]
+    _ = foo == &0;
+    //~^ERROR can't compare `&{integer}` with `{integer}` [E0277]
+    _ = &&&&foo == 0;
+    //~^ERROR can't compare `&&&&&&{integer}` with `{integer}` [E0277]
+    _ = *foo == 0;
+    //~^ERROR can't compare `&{integer}` with `{integer}` [E0277]
+    _ = &&foo == &&0;
+    //~^ERROR can't compare `&&{integer}` with `{integer}` [E0277]
+    _ = &Box::new(42) == 42;
+    //~^ERROR can't compare `&Box<{integer}>` with `{integer}` [E0277]
+    _ = &Box::new(&Box::new(&42)) == 42;
+    //~^ERROR can't compare `&Box<&Box<&{integer}>>` with `{integer}` [E0277]
+
+    // Dereference RHS
+    _ = 0 == foo;
+    //~^ERROR can't compare `{integer}` with `&&{integer}` [E0277]
+    _ = &0 == foo;
+    //~^ERROR can't compare `{integer}` with `&{integer}` [E0277]
+    _ = 0 == &&&&foo;
+    //~^ERROR can't compare `{integer}` with `&&&&&&{integer}` [E0277]
+    _ = 0 == *foo;
+    //~^ERROR can't compare `{integer}` with `&{integer}` [E0277]
+    _ = &&0 == &&foo;
+    //~^ERROR can't compare `{integer}` with `&&{integer}` [E0277]
+
+    // Dereference both sides
+    _ = &Box::new(Box::new(42)) == &foo;
+    //~^ERROR can't compare `Box<Box<{integer}>>` with `&&{integer}` [E0277]
+    _ = &Box::new(42) == &foo;
+    //~^ERROR can't compare `Box<{integer}>` with `&&{integer}` [E0277]
+    _ = &Box::new(Box::new(Box::new(Box::new(42)))) == &foo;
+    //~^ERROR can't compare `Box<Box<Box<Box<{integer}>>>>` with `&&{integer}` [E0277]
+    _ = &foo == &Box::new(Box::new(Box::new(Box::new(42))));
+    //~^ERROR can't compare `&&{integer}` with `Box<Box<Box<Box<{integer}>>>>` [E0277]
+
+    // Don't suggest dereferencing the LHS; suggest boxing the RHS instead
+    _ = Box::new(42) == 42;
+    //~^ERROR mismatched types [E0308]
+
+    // Don't suggest dereferencing with types that can't be compared
+    struct Foo;
+    _ = &&0 == Foo;
+    //~^ERROR can't compare `&&{integer}` with `Foo` [E0277]
+    _ = Foo == &&0;
+    //~^ERROR binary operation `==` cannot be applied to type `Foo` [E0369]
+}
+
+fn baz() {
+    // Issue #44695
+    let owned = "foo".to_owned();
+    let string_ref = &owned;
+    let partial = "foobar";
+    _ = string_ref == partial[..3];
+    //~^ERROR can't compare `&String` with `str` [E0277]
+    _ = partial[..3] == string_ref;
+    //~^ERROR can't compare `str` with `&String` [E0277]
+}
+
+fn main() {}
diff --git a/tests/ui/binop/binary-op-suggest-deref.stderr b/tests/ui/binop/binary-op-suggest-deref.stderr
index d1d0089ece7..f5de64e3ab1 100644
--- a/tests/ui/binop/binary-op-suggest-deref.stderr
+++ b/tests/ui/binop/binary-op-suggest-deref.stderr
@@ -9,6 +9,298 @@ help: consider dereferencing the borrow
 LL |     if *i < 0 {}
    |        +
 
-error: aborting due to 1 previous error
+error[E0277]: can't compare `&&{integer}` with `{integer}`
+  --> $DIR/binary-op-suggest-deref.rs:15:13
+   |
+LL |     _ = foo == 0;
+   |             ^^ no implementation for `&&{integer} == {integer}`
+   |
+   = help: the trait `PartialEq<{integer}>` is not implemented for `&&{integer}`
+help: consider dereferencing here
+   |
+LL |     _ = **foo == 0;
+   |         ++
+
+error[E0277]: can't compare `&{integer}` with `{integer}`
+  --> $DIR/binary-op-suggest-deref.rs:17:13
+   |
+LL |     _ = foo == &0;
+   |             ^^ no implementation for `&{integer} == {integer}`
+   |
+   = help: the trait `PartialEq<{integer}>` is not implemented for `&{integer}`
+   = note: required for `&&{integer}` to implement `PartialEq<&{integer}>`
+help: consider dereferencing here
+   |
+LL |     _ = *foo == &0;
+   |         +
+
+error[E0277]: can't compare `&&&&&&{integer}` with `{integer}`
+  --> $DIR/binary-op-suggest-deref.rs:19:17
+   |
+LL |     _ = &&&&foo == 0;
+   |                 ^^ no implementation for `&&&&&&{integer} == {integer}`
+   |
+   = help: the trait `PartialEq<{integer}>` is not implemented for `&&&&&&{integer}`
+help: consider removing the borrows and dereferencing instead
+   |
+LL -     _ = &&&&foo == 0;
+LL +     _ = **foo == 0;
+   |
+
+error[E0277]: can't compare `&{integer}` with `{integer}`
+  --> $DIR/binary-op-suggest-deref.rs:21:14
+   |
+LL |     _ = *foo == 0;
+   |              ^^ no implementation for `&{integer} == {integer}`
+   |
+   = help: the trait `PartialEq<{integer}>` is not implemented for `&{integer}`
+help: consider dereferencing here
+   |
+LL |     _ = **foo == 0;
+   |         +
+
+error[E0277]: can't compare `&&{integer}` with `{integer}`
+  --> $DIR/binary-op-suggest-deref.rs:23:15
+   |
+LL |     _ = &&foo == &&0;
+   |               ^^ no implementation for `&&{integer} == {integer}`
+   |
+   = help: the trait `PartialEq<{integer}>` is not implemented for `&&{integer}`
+   = note: required for `&&&{integer}` to implement `PartialEq<&{integer}>`
+   = note: 1 redundant requirement hidden
+   = note: required for `&&&&{integer}` to implement `PartialEq<&&{integer}>`
+help: consider removing the borrows
+   |
+LL -     _ = &&foo == &&0;
+LL +     _ = foo == &&0;
+   |
+
+error[E0277]: can't compare `&Box<{integer}>` with `{integer}`
+  --> $DIR/binary-op-suggest-deref.rs:25:23
+   |
+LL |     _ = &Box::new(42) == 42;
+   |                       ^^ no implementation for `&Box<{integer}> == {integer}`
+   |
+   = help: the trait `PartialEq<{integer}>` is not implemented for `&Box<{integer}>`
+help: consider removing the borrow and dereferencing instead
+   |
+LL -     _ = &Box::new(42) == 42;
+LL +     _ = *Box::new(42) == 42;
+   |
+
+error[E0277]: can't compare `&Box<&Box<&{integer}>>` with `{integer}`
+  --> $DIR/binary-op-suggest-deref.rs:27:35
+   |
+LL |     _ = &Box::new(&Box::new(&42)) == 42;
+   |                                   ^^ no implementation for `&Box<&Box<&{integer}>> == {integer}`
+   |
+   = help: the trait `PartialEq<{integer}>` is not implemented for `&Box<&Box<&{integer}>>`
+help: consider removing the borrow and dereferencing instead
+   |
+LL -     _ = &Box::new(&Box::new(&42)) == 42;
+LL +     _ = ****Box::new(&Box::new(&42)) == 42;
+   |
+
+error[E0277]: can't compare `{integer}` with `&&{integer}`
+  --> $DIR/binary-op-suggest-deref.rs:31:11
+   |
+LL |     _ = 0 == foo;
+   |           ^^ no implementation for `{integer} == &&{integer}`
+   |
+   = help: the trait `PartialEq<&&{integer}>` is not implemented for `{integer}`
+help: consider dereferencing here
+   |
+LL |     _ = 0 == **foo;
+   |              ++
+
+error[E0277]: can't compare `{integer}` with `&{integer}`
+  --> $DIR/binary-op-suggest-deref.rs:33:12
+   |
+LL |     _ = &0 == foo;
+   |            ^^ no implementation for `{integer} == &{integer}`
+   |
+   = help: the trait `PartialEq<&{integer}>` is not implemented for `{integer}`
+   = note: required for `&{integer}` to implement `PartialEq<&&{integer}>`
+help: consider dereferencing here
+   |
+LL |     _ = &0 == *foo;
+   |               +
+
+error[E0277]: can't compare `{integer}` with `&&&&&&{integer}`
+  --> $DIR/binary-op-suggest-deref.rs:35:11
+   |
+LL |     _ = 0 == &&&&foo;
+   |           ^^ no implementation for `{integer} == &&&&&&{integer}`
+   |
+   = help: the trait `PartialEq<&&&&&&{integer}>` is not implemented for `{integer}`
+help: consider removing the borrows and dereferencing instead
+   |
+LL -     _ = 0 == &&&&foo;
+LL +     _ = 0 == **foo;
+   |
+
+error[E0277]: can't compare `{integer}` with `&{integer}`
+  --> $DIR/binary-op-suggest-deref.rs:37:11
+   |
+LL |     _ = 0 == *foo;
+   |           ^^ no implementation for `{integer} == &{integer}`
+   |
+   = help: the trait `PartialEq<&{integer}>` is not implemented for `{integer}`
+help: consider dereferencing here
+   |
+LL |     _ = 0 == **foo;
+   |              +
+
+error[E0277]: can't compare `{integer}` with `&&{integer}`
+  --> $DIR/binary-op-suggest-deref.rs:39:13
+   |
+LL |     _ = &&0 == &&foo;
+   |             ^^ no implementation for `{integer} == &&{integer}`
+   |
+   = help: the trait `PartialEq<&&{integer}>` is not implemented for `{integer}`
+   = note: required for `&{integer}` to implement `PartialEq<&&&{integer}>`
+   = note: 1 redundant requirement hidden
+   = note: required for `&&{integer}` to implement `PartialEq<&&&&{integer}>`
+help: consider removing the borrows
+   |
+LL -     _ = &&0 == &&foo;
+LL +     _ = &&0 == foo;
+   |
+
+error[E0277]: can't compare `Box<Box<{integer}>>` with `&&{integer}`
+  --> $DIR/binary-op-suggest-deref.rs:43:33
+   |
+LL |     _ = &Box::new(Box::new(42)) == &foo;
+   |                                 ^^ no implementation for `Box<Box<{integer}>> == &&{integer}`
+   |
+   = help: the trait `PartialEq<&&{integer}>` is not implemented for `Box<Box<{integer}>>`
+   = note: required for `&Box<Box<{integer}>>` to implement `PartialEq<&&&{integer}>`
+help: consider dereferencing both sides of the expression
+   |
+LL -     _ = &Box::new(Box::new(42)) == &foo;
+LL +     _ = **Box::new(Box::new(42)) == **foo;
+   |
+
+error[E0277]: can't compare `Box<{integer}>` with `&&{integer}`
+  --> $DIR/binary-op-suggest-deref.rs:45:23
+   |
+LL |     _ = &Box::new(42) == &foo;
+   |                       ^^ no implementation for `Box<{integer}> == &&{integer}`
+   |
+   = help: the trait `PartialEq<&&{integer}>` is not implemented for `Box<{integer}>`
+   = note: required for `&Box<{integer}>` to implement `PartialEq<&&&{integer}>`
+help: consider dereferencing both sides of the expression
+   |
+LL -     _ = &Box::new(42) == &foo;
+LL +     _ = *Box::new(42) == **foo;
+   |
+
+error[E0277]: can't compare `Box<Box<Box<Box<{integer}>>>>` with `&&{integer}`
+  --> $DIR/binary-op-suggest-deref.rs:47:53
+   |
+LL |     _ = &Box::new(Box::new(Box::new(Box::new(42)))) == &foo;
+   |                                                     ^^ no implementation for `Box<Box<Box<Box<{integer}>>>> == &&{integer}`
+   |
+   = help: the trait `PartialEq<&&{integer}>` is not implemented for `Box<Box<Box<Box<{integer}>>>>`
+   = note: required for `&Box<Box<Box<Box<{integer}>>>>` to implement `PartialEq<&&&{integer}>`
+help: consider dereferencing both sides of the expression
+   |
+LL -     _ = &Box::new(Box::new(Box::new(Box::new(42)))) == &foo;
+LL +     _ = ****Box::new(Box::new(Box::new(Box::new(42)))) == **foo;
+   |
+
+error[E0277]: can't compare `&&{integer}` with `Box<Box<Box<Box<{integer}>>>>`
+  --> $DIR/binary-op-suggest-deref.rs:49:14
+   |
+LL |     _ = &foo == &Box::new(Box::new(Box::new(Box::new(42))));
+   |              ^^ no implementation for `&&{integer} == Box<Box<Box<Box<{integer}>>>>`
+   |
+   = help: the trait `PartialEq<Box<Box<Box<Box<{integer}>>>>>` is not implemented for `&&{integer}`
+   = note: required for `&&&{integer}` to implement `PartialEq<&Box<Box<Box<Box<{integer}>>>>>`
+help: consider dereferencing both sides of the expression
+   |
+LL -     _ = &foo == &Box::new(Box::new(Box::new(Box::new(42))));
+LL +     _ = **foo == ****Box::new(Box::new(Box::new(Box::new(42))));
+   |
+
+error[E0308]: mismatched types
+  --> $DIR/binary-op-suggest-deref.rs:53:25
+   |
+LL |     _ = Box::new(42) == 42;
+   |         ------------    ^^ expected `Box<{integer}>`, found integer
+   |         |
+   |         expected because this is `Box<{integer}>`
+   |
+   = note: expected struct `Box<{integer}>`
+                found type `{integer}`
+   = note: for more on the distinction between the stack and the heap, read https://doc.rust-lang.org/book/ch15-01-box.html, https://doc.rust-lang.org/rust-by-example/std/box.html, and https://doc.rust-lang.org/std/boxed/index.html
+help: store this in the heap by calling `Box::new`
+   |
+LL |     _ = Box::new(42) == Box::new(42);
+   |                         +++++++++  +
+
+error[E0277]: can't compare `&&{integer}` with `Foo`
+  --> $DIR/binary-op-suggest-deref.rs:58:13
+   |
+LL |     _ = &&0 == Foo;
+   |             ^^ no implementation for `&&{integer} == Foo`
+   |
+   = help: the trait `PartialEq<Foo>` is not implemented for `&&{integer}`
+   = help: the following other types implement trait `PartialEq<Rhs>`:
+             isize
+             i8
+             i16
+             i32
+             i64
+             i128
+             usize
+             u8
+           and 6 others
+
+error[E0369]: binary operation `==` cannot be applied to type `Foo`
+  --> $DIR/binary-op-suggest-deref.rs:60:13
+   |
+LL |     _ = Foo == &&0;
+   |         --- ^^ --- &&{integer}
+   |         |
+   |         Foo
+   |
+note: an implementation of `PartialEq<&&{integer}>` might be missing for `Foo`
+  --> $DIR/binary-op-suggest-deref.rs:57:5
+   |
+LL |     struct Foo;
+   |     ^^^^^^^^^^ must implement `PartialEq<&&{integer}>`
+help: consider annotating `Foo` with `#[derive(PartialEq)]`
+   |
+LL +     #[derive(PartialEq)]
+LL |     struct Foo;
+   |
+
+error[E0277]: can't compare `&String` with `str`
+  --> $DIR/binary-op-suggest-deref.rs:69:20
+   |
+LL |     _ = string_ref == partial[..3];
+   |                    ^^ no implementation for `&String == str`
+   |
+   = help: the trait `PartialEq<str>` is not implemented for `&String`
+help: consider dereferencing here
+   |
+LL |     _ = *string_ref == partial[..3];
+   |         +
+
+error[E0277]: can't compare `str` with `&String`
+  --> $DIR/binary-op-suggest-deref.rs:71:22
+   |
+LL |     _ = partial[..3] == string_ref;
+   |                      ^^ no implementation for `str == &String`
+   |
+   = help: the trait `PartialEq<&String>` is not implemented for `str`
+help: consider dereferencing here
+   |
+LL |     _ = partial[..3] == *string_ref;
+   |                         +
+
+error: aborting due to 22 previous errors
 
-For more information about this error, try `rustc --explain E0308`.
+Some errors have detailed explanations: E0277, E0308, E0369.
+For more information about an error, try `rustc --explain E0277`.
diff --git a/tests/ui/dst/issue-113447.fixed b/tests/ui/dst/issue-113447.fixed
deleted file mode 100644
index 536f680f697..00000000000
--- a/tests/ui/dst/issue-113447.fixed
+++ /dev/null
@@ -1,25 +0,0 @@
-// run-rustfix
-
-pub struct Bytes;
-
-impl Bytes {
-    pub fn as_slice(&self) -> &[u8] {
-        todo!()
-    }
-}
-
-impl PartialEq<[u8]> for Bytes {
-    fn eq(&self, other: &[u8]) -> bool {
-        self.as_slice() == other
-    }
-}
-
-impl PartialEq<Bytes> for &[u8] {
-    fn eq(&self, other: &Bytes) -> bool {
-        *other == **self
-    }
-}
-
-fn main() {
-    let _ = &[0u8] == &[0xAA][..]; //~ ERROR can't compare `&[u8; 1]` with `[{integer}; 1]`
-}
diff --git a/tests/ui/dst/issue-113447.rs b/tests/ui/dst/issue-113447.rs
index c10a4f2ff8e..75156a117e9 100644
--- a/tests/ui/dst/issue-113447.rs
+++ b/tests/ui/dst/issue-113447.rs
@@ -1,5 +1,3 @@
-// run-rustfix
-
 pub struct Bytes;
 
 impl Bytes {
diff --git a/tests/ui/dst/issue-113447.stderr b/tests/ui/dst/issue-113447.stderr
index 266eb228046..4d0ed33a643 100644
--- a/tests/ui/dst/issue-113447.stderr
+++ b/tests/ui/dst/issue-113447.stderr
@@ -1,24 +1,15 @@
 error[E0277]: can't compare `&[u8; 1]` with `[{integer}; 1]`
-  --> $DIR/issue-113447.rs:24:20
+  --> $DIR/issue-113447.rs:22:20
    |
 LL |     let _ = &[0u8] == [0xAA];
    |                    ^^ no implementation for `&[u8; 1] == [{integer}; 1]`
    |
    = help: the trait `PartialEq<[{integer}; 1]>` is not implemented for `&[u8; 1]`
-   = help: the following other types implement trait `PartialEq<Rhs>`:
-             <[A; N] as PartialEq<[B; N]>>
-             <[A; N] as PartialEq<[B]>>
-             <[A; N] as PartialEq<&[B]>>
-             <[A; N] as PartialEq<&mut [B]>>
-             <[T] as PartialEq<Vec<U, A>>>
-             <[A] as PartialEq<[B]>>
-             <[B] as PartialEq<[A; N]>>
-             <&[u8] as PartialEq<Bytes>>
-           and 4 others
-help: convert the array to a `&[u8]` slice instead
+help: consider removing the borrow
+   |
+LL -     let _ = &[0u8] == [0xAA];
+LL +     let _ = [0u8] == [0xAA];
    |
-LL |     let _ = &[0u8] == &[0xAA][..];
-   |                       +      ++++
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/partialeq_help.stderr b/tests/ui/partialeq_help.stderr
index fdff94f425c..f5de1308e87 100644
--- a/tests/ui/partialeq_help.stderr
+++ b/tests/ui/partialeq_help.stderr
@@ -5,6 +5,10 @@ LL |     a == b;
    |       ^^ no implementation for `&T == T`
    |
    = help: the trait `PartialEq<T>` is not implemented for `&T`
+help: consider dereferencing here
+   |
+LL |     *a == b;
+   |     +
 help: consider introducing a `where` clause, but there might be an alternative better way to express this requirement
    |
 LL | fn foo<T: PartialEq>(a: &T, b: T) where &T: PartialEq<T> {
@@ -17,6 +21,10 @@ LL |     a == b;
    |       ^^ no implementation for `&T == T`
    |
    = help: the trait `PartialEq<T>` is not implemented for `&T`
+help: consider dereferencing here
+   |
+LL |     *a == b;
+   |     +
 help: consider extending the `where` clause, but there might be an alternative better way to express this requirement
    |
 LL | fn foo2<T: PartialEq>(a: &T, b: T) where &T: PartialEq<T> {