1 use clippy_utils::diagnostics::span_lint_and_then;
2 use clippy_utils::source::snippet;
3 use clippy_utils::ty::{implements_trait, is_copy};
4 use clippy_utils::{match_def_path, path_def_id, paths};
5 use rustc_errors::Applicability;
6 use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
7 use rustc_lint::LateContext;
8 use rustc_middle::ty::Ty;
9 use rustc_span::symbol::sym;
13 pub(super) fn check(cx: &LateContext<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &Expr<'_>) {
14 if op.is_comparison() {
15 check_op(cx, lhs, rhs, true);
16 check_op(cx, rhs, lhs, false);
26 fn is_implemented(&self) -> bool {
27 self.ty_eq_other || self.other_eq_ty
31 fn symmetric_partial_eq<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, other: Ty<'tcx>) -> Option<EqImpl> {
32 cx.tcx.lang_items().eq_trait().map(|def_id| EqImpl {
33 ty_eq_other: implements_trait(cx, ty, def_id, &[other.into()]),
34 other_eq_ty: implements_trait(cx, other, def_id, &[ty.into()]),
38 fn check_op(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) {
39 let typeck = cx.typeck_results();
40 let (arg, arg_span) = match expr.kind {
41 ExprKind::MethodCall(_, arg, [], _)
43 .type_dependent_def_id(expr.hir_id)
44 .and_then(|id| cx.tcx.trait_of_item(id))
46 matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ToString | sym::ToOwned))
51 ExprKind::Call(path, [arg])
52 if path_def_id(cx, path).map_or(false, |id| {
53 if match_def_path(cx, id, &paths::FROM_STR_METHOD) {
55 } else if cx.tcx.lang_items().from_fn() == Some(id) {
56 !is_copy(cx, typeck.expr_ty(expr))
67 let arg_ty = typeck.expr_ty(arg);
68 let other_ty = typeck.expr_ty(other);
70 let without_deref = symmetric_partial_eq(cx, arg_ty, other_ty).unwrap_or_default();
71 let with_deref = arg_ty
73 .and_then(|tam| symmetric_partial_eq(cx, tam.ty, other_ty))
76 if !with_deref.is_implemented() && !without_deref.is_implemented() {
80 let other_gets_derefed = matches!(other.kind, ExprKind::Unary(UnOp::Deref, _));
82 let lint_span = if other_gets_derefed {
83 expr.span.to(other.span)
92 "this creates an owned instance just for comparison",
94 // This also catches `PartialEq` implementations that call `to_owned`.
95 if other_gets_derefed {
96 diag.span_label(lint_span, "try implementing the comparison without allocating");
100 let arg_snip = snippet(cx, arg_span, "..");
103 if with_deref.is_implemented() {
104 expr_snip = format!("*{arg_snip}");
105 eq_impl = with_deref;
107 expr_snip = arg_snip.to_string();
108 eq_impl = without_deref;
113 if (eq_impl.ty_eq_other && left) || (eq_impl.other_eq_ty && !left) {
117 span = expr.span.to(other.span);
119 let cmp_span = if other.span < expr.span {
120 other.span.between(expr.span)
122 expr.span.between(other.span)
124 if eq_impl.ty_eq_other {
127 snippet(cx, cmp_span, ".."),
128 snippet(cx, other.span, "..")
133 snippet(cx, other.span, ".."),
134 snippet(cx, cmp_span, "..")
139 diag.span_suggestion(
143 Applicability::MachineApplicable, // snippet