]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/operators/cmp_owned.rs
Rollup merge of #103305 - c410-f3r:moar-errors, r=petrochenkov
[rust.git] / src / tools / clippy / clippy_lints / src / operators / cmp_owned.rs
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;
10
11 use super::CMP_OWNED;
12
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);
17     }
18 }
19
20 #[derive(Default)]
21 struct EqImpl {
22     ty_eq_other: bool,
23     other_eq_ty: bool,
24 }
25 impl EqImpl {
26     fn is_implemented(&self) -> bool {
27         self.ty_eq_other || self.other_eq_ty
28     }
29 }
30
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()]),
35     })
36 }
37
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, [], _)
42             if typeck
43                 .type_dependent_def_id(expr.hir_id)
44                 .and_then(|id| cx.tcx.trait_of_item(id))
45                 .map_or(false, |id| {
46                     matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ToString | sym::ToOwned))
47                 }) =>
48         {
49             (arg, arg.span)
50         },
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 {
55                     0 => true,
56                     1 => !is_copy(cx, typeck.expr_ty(expr)),
57                     _ => false,
58                 }) =>
59         {
60             (arg, arg.span)
61         },
62         _ => return,
63     };
64
65     let arg_ty = typeck.expr_ty(arg);
66     let other_ty = typeck.expr_ty(other);
67
68     let without_deref = symmetric_partial_eq(cx, arg_ty, other_ty).unwrap_or_default();
69     let with_deref = arg_ty
70         .builtin_deref(true)
71         .and_then(|tam| symmetric_partial_eq(cx, tam.ty, other_ty))
72         .unwrap_or_default();
73
74     if !with_deref.is_implemented() && !without_deref.is_implemented() {
75         return;
76     }
77
78     let other_gets_derefed = matches!(other.kind, ExprKind::Unary(UnOp::Deref, _));
79
80     let lint_span = if other_gets_derefed {
81         expr.span.to(other.span)
82     } else {
83         expr.span
84     };
85
86     span_lint_and_then(
87         cx,
88         CMP_OWNED,
89         lint_span,
90         "this creates an owned instance just for comparison",
91         |diag| {
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");
95                 return;
96             }
97
98             let arg_snip = snippet(cx, arg_span, "..");
99             let expr_snip;
100             let eq_impl;
101             if with_deref.is_implemented() {
102                 expr_snip = format!("*{arg_snip}");
103                 eq_impl = with_deref;
104             } else {
105                 expr_snip = arg_snip.to_string();
106                 eq_impl = without_deref;
107             };
108
109             let span;
110             let hint;
111             if (eq_impl.ty_eq_other && left) || (eq_impl.other_eq_ty && !left) {
112                 span = expr.span;
113                 hint = expr_snip;
114             } else {
115                 span = expr.span.to(other.span);
116
117                 let cmp_span = if other.span < expr.span {
118                     other.span.between(expr.span)
119                 } else {
120                     expr.span.between(other.span)
121                 };
122                 if eq_impl.ty_eq_other {
123                     hint = format!(
124                         "{expr_snip}{}{}",
125                         snippet(cx, cmp_span, ".."),
126                         snippet(cx, other.span, "..")
127                     );
128                 } else {
129                     hint = format!(
130                         "{}{}{expr_snip}",
131                         snippet(cx, other.span, ".."),
132                         snippet(cx, cmp_span, "..")
133                     );
134                 }
135             }
136
137             diag.span_suggestion(
138                 span,
139                 "try",
140                 hint,
141                 Applicability::MachineApplicable, // snippet
142             );
143         },
144     );
145 }