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_any_def_paths, 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)
53 .and_then(|id| match_any_def_paths(cx, id, &[&paths::FROM_STR_METHOD, &paths::FROM_FROM]))
54 .map_or(false, |idx| match idx {
56 1 => !is_copy(cx, typeck.expr_ty(expr)),
65 let arg_ty = typeck.expr_ty(arg);
66 let other_ty = typeck.expr_ty(other);
68 let without_deref = symmetric_partial_eq(cx, arg_ty, other_ty).unwrap_or_default();
69 let with_deref = arg_ty
71 .and_then(|tam| symmetric_partial_eq(cx, tam.ty, other_ty))
74 if !with_deref.is_implemented() && !without_deref.is_implemented() {
78 let other_gets_derefed = matches!(other.kind, ExprKind::Unary(UnOp::Deref, _));
80 let lint_span = if other_gets_derefed {
81 expr.span.to(other.span)
90 "this creates an owned instance just for comparison",
92 // This also catches `PartialEq` implementations that call `to_owned`.
93 if other_gets_derefed {
94 diag.span_label(lint_span, "try implementing the comparison without allocating");
98 let arg_snip = snippet(cx, arg_span, "..");
101 if with_deref.is_implemented() {
102 expr_snip = format!("*{arg_snip}");
103 eq_impl = with_deref;
105 expr_snip = arg_snip.to_string();
106 eq_impl = without_deref;
111 if (eq_impl.ty_eq_other && left) || (eq_impl.other_eq_ty && !left) {
115 span = expr.span.to(other.span);
117 let cmp_span = if other.span < expr.span {
118 other.span.between(expr.span)
120 expr.span.between(other.span)
122 if eq_impl.ty_eq_other {
125 snippet(cx, cmp_span, ".."),
126 snippet(cx, other.span, "..")
131 snippet(cx, other.span, ".."),
132 snippet(cx, cmp_span, "..")
137 diag.span_suggestion(
141 Applicability::MachineApplicable, // snippet