| use clippy_utils::diagnostics::span_lint_and_then; | |
| use clippy_utils::get_enclosing_block; | |
| use clippy_utils::source::snippet_with_context; | |
| use clippy_utils::ty::{implements_trait, is_copy}; | |
| use rustc_errors::Applicability; | |
| use rustc_hir::def::Res; | |
| use rustc_hir::def_id::DefId; | |
| use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, GenericArg, ItemKind, QPath, TyKind}; | |
| use rustc_lint::LateContext; | |
| use rustc_middle::ty::{self, Ty}; | |
| use super::OP_REF; | |
| #[expect(clippy::too_many_lines)] | |
| pub(crate) fn check<'tcx>( | |
| cx: &LateContext<'tcx>, | |
| e: &'tcx Expr<'_>, | |
| op: BinOpKind, | |
| left: &'tcx Expr<'_>, | |
| right: &'tcx Expr<'_>, | |
| ) { | |
| let (trait_id, requires_ref) = match op { | |
| BinOpKind::Add => (cx.tcx.lang_items().add_trait(), false), | |
| BinOpKind::Sub => (cx.tcx.lang_items().sub_trait(), false), | |
| BinOpKind::Mul => (cx.tcx.lang_items().mul_trait(), false), | |
| BinOpKind::Div => (cx.tcx.lang_items().div_trait(), false), | |
| BinOpKind::Rem => (cx.tcx.lang_items().rem_trait(), false), | |
| // don't lint short circuiting ops | |
| BinOpKind::And | BinOpKind::Or => return, | |
| BinOpKind::BitXor => (cx.tcx.lang_items().bitxor_trait(), false), | |
| BinOpKind::BitAnd => (cx.tcx.lang_items().bitand_trait(), false), | |
| BinOpKind::BitOr => (cx.tcx.lang_items().bitor_trait(), false), | |
| BinOpKind::Shl => (cx.tcx.lang_items().shl_trait(), false), | |
| BinOpKind::Shr => (cx.tcx.lang_items().shr_trait(), false), | |
| BinOpKind::Ne | BinOpKind::Eq => (cx.tcx.lang_items().eq_trait(), true), | |
| BinOpKind::Lt | BinOpKind::Le | BinOpKind::Ge | BinOpKind::Gt => { | |
| (cx.tcx.lang_items().partial_ord_trait(), true) | |
| }, | |
| }; | |
| if let Some(trait_id) = trait_id { | |
| match (&left.kind, &right.kind) { | |
| // do not suggest to dereference literals | |
| (&ExprKind::Lit(..), _) | (_, &ExprKind::Lit(..)) => {}, | |
| // &foo == &bar | |
| (&ExprKind::AddrOf(BorrowKind::Ref, _, l), &ExprKind::AddrOf(BorrowKind::Ref, _, r)) => { | |
| let lty = cx.typeck_results().expr_ty(l); | |
| let rty = cx.typeck_results().expr_ty(r); | |
| let lcpy = is_copy(cx, lty); | |
| let rcpy = is_copy(cx, rty); | |
| if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) | |
| && ((are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty)) | |
| || (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty))) | |
| { | |
| return; // Don't lint | |
| } | |
| // either operator autorefs or both args are copyable | |
| if (requires_ref || (lcpy && rcpy)) && implements_trait(cx, lty, trait_id, &[rty.into()]) { | |
| span_lint_and_then( | |
| cx, | |
| OP_REF, | |
| e.span, | |
| "needlessly taken reference of both operands", | |
| |diag| { | |
| let mut applicability = Applicability::MachineApplicable; | |
| let (lsnip, _) = snippet_with_context(cx, l.span, e.span.ctxt(), "...", &mut applicability); | |
| let (rsnip, _) = snippet_with_context(cx, r.span, e.span.ctxt(), "...", &mut applicability); | |
| diag.multipart_suggestion( | |
| "use the values directly", | |
| vec![(left.span, lsnip.to_string()), (right.span, rsnip.to_string())], | |
| applicability, | |
| ); | |
| }, | |
| ); | |
| } else if lcpy | |
| && !rcpy | |
| && implements_trait(cx, lty, trait_id, &[cx.typeck_results().expr_ty(right).into()]) | |
| { | |
| span_lint_and_then( | |
| cx, | |
| OP_REF, | |
| e.span, | |
| "needlessly taken reference of left operand", | |
| |diag| { | |
| let mut applicability = Applicability::MachineApplicable; | |
| let (lsnip, _) = snippet_with_context(cx, l.span, e.span.ctxt(), "...", &mut applicability); | |
| diag.span_suggestion(left.span, "use the left value directly", lsnip, applicability); | |
| }, | |
| ); | |
| } else if !lcpy | |
| && rcpy | |
| && implements_trait(cx, cx.typeck_results().expr_ty(left), trait_id, &[rty.into()]) | |
| { | |
| span_lint_and_then( | |
| cx, | |
| OP_REF, | |
| e.span, | |
| "needlessly taken reference of right operand", | |
| |diag| { | |
| let mut applicability = Applicability::MachineApplicable; | |
| let (rsnip, _) = snippet_with_context(cx, r.span, e.span.ctxt(), "...", &mut applicability); | |
| diag.span_suggestion( | |
| right.span, | |
| "use the right value directly", | |
| rsnip, | |
| Applicability::MachineApplicable, | |
| ); | |
| }, | |
| ); | |
| } | |
| }, | |
| // &foo == bar | |
| (&ExprKind::AddrOf(BorrowKind::Ref, _, l), _) => { | |
| let lty = cx.typeck_results().expr_ty(l); | |
| if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) { | |
| let rty = cx.typeck_results().expr_ty(right); | |
| if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty)) | |
| || (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty)) | |
| { | |
| return; // Don't lint | |
| } | |
| } | |
| let lcpy = is_copy(cx, lty); | |
| if (requires_ref || lcpy) | |
| && implements_trait(cx, lty, trait_id, &[cx.typeck_results().expr_ty(right).into()]) | |
| { | |
| span_lint_and_then( | |
| cx, | |
| OP_REF, | |
| e.span, | |
| "needlessly taken reference of left operand", | |
| |diag| { | |
| let mut applicability = Applicability::MachineApplicable; | |
| let (lsnip, _) = snippet_with_context(cx, l.span, e.span.ctxt(), "...", &mut applicability); | |
| diag.span_suggestion( | |
| left.span, | |
| "use the left value directly", | |
| lsnip, | |
| Applicability::MachineApplicable, | |
| ); | |
| }, | |
| ); | |
| } | |
| }, | |
| // foo == &bar | |
| (_, &ExprKind::AddrOf(BorrowKind::Ref, _, r)) => { | |
| let rty = cx.typeck_results().expr_ty(r); | |
| if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) { | |
| let lty = cx.typeck_results().expr_ty(left); | |
| if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty)) | |
| || (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty)) | |
| { | |
| return; // Don't lint | |
| } | |
| } | |
| let rcpy = is_copy(cx, rty); | |
| if (requires_ref || rcpy) | |
| && implements_trait(cx, cx.typeck_results().expr_ty(left), trait_id, &[rty.into()]) | |
| { | |
| span_lint_and_then(cx, OP_REF, e.span, "taken reference of right operand", |diag| { | |
| let mut applicability = Applicability::MachineApplicable; | |
| let (rsnip, _) = snippet_with_context(cx, r.span, e.span.ctxt(), "...", &mut applicability); | |
| diag.span_suggestion( | |
| right.span, | |
| "use the right value directly", | |
| rsnip, | |
| Applicability::MachineApplicable, | |
| ); | |
| }); | |
| } | |
| }, | |
| _ => {}, | |
| } | |
| } | |
| } | |
| fn in_impl<'tcx>( | |
| cx: &LateContext<'tcx>, | |
| e: &'tcx Expr<'_>, | |
| bin_op: DefId, | |
| ) -> Option<(&'tcx rustc_hir::Ty<'tcx>, &'tcx rustc_hir::Ty<'tcx>)> { | |
| if let Some(block) = get_enclosing_block(cx, e.hir_id) | |
| && let Some(impl_def_id) = cx.tcx.impl_of_assoc(block.hir_id.owner.to_def_id()) | |
| && let item = cx.tcx.hir_expect_item(impl_def_id.expect_local()) | |
| && let ItemKind::Impl(item) = &item.kind | |
| && let Some(of_trait) = &item.of_trait | |
| && let Some(seg) = of_trait.trait_ref.path.segments.last() | |
| && let Res::Def(_, trait_id) = seg.res | |
| && trait_id == bin_op | |
| && let Some(generic_args) = seg.args | |
| && let Some(GenericArg::Type(other_ty)) = generic_args.args.last() | |
| { | |
| // `_` is not permitted in impl headers | |
| Some((item.self_ty, other_ty.as_unambig_ty())) | |
| } else { | |
| None | |
| } | |
| } | |
| fn are_equal(cx: &LateContext<'_>, middle_ty: Ty<'_>, hir_ty: &rustc_hir::Ty<'_>) -> bool { | |
| if let ty::Adt(adt_def, _) = middle_ty.kind() | |
| && let Some(local_did) = adt_def.did().as_local() | |
| && let item = cx.tcx.hir_expect_item(local_did) | |
| && let middle_ty_id = item.owner_id.to_def_id() | |
| && let TyKind::Path(QPath::Resolved(_, path)) = hir_ty.kind | |
| && let Res::Def(_, hir_ty_id) = path.res | |
| { | |
| hir_ty_id == middle_ty_id | |
| } else { | |
| false | |
| } | |
| } |