]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/operators/cmp_owned.rs
Rollup merge of #103287 - saethlin:faster-len-check, r=thomcc
[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_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;
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).map_or(false, |id| {
53                 if match_def_path(cx, id, &paths::FROM_STR_METHOD) {
54                     true
55                 } else if cx.tcx.lang_items().from_fn() == Some(id) {
56                     !is_copy(cx, typeck.expr_ty(expr))
57                 } else {
58                     false
59                 }
60             }) =>
61         {
62             (arg, arg.span)
63         },
64         _ => return,
65     };
66
67     let arg_ty = typeck.expr_ty(arg);
68     let other_ty = typeck.expr_ty(other);
69
70     let without_deref = symmetric_partial_eq(cx, arg_ty, other_ty).unwrap_or_default();
71     let with_deref = arg_ty
72         .builtin_deref(true)
73         .and_then(|tam| symmetric_partial_eq(cx, tam.ty, other_ty))
74         .unwrap_or_default();
75
76     if !with_deref.is_implemented() && !without_deref.is_implemented() {
77         return;
78     }
79
80     let other_gets_derefed = matches!(other.kind, ExprKind::Unary(UnOp::Deref, _));
81
82     let lint_span = if other_gets_derefed {
83         expr.span.to(other.span)
84     } else {
85         expr.span
86     };
87
88     span_lint_and_then(
89         cx,
90         CMP_OWNED,
91         lint_span,
92         "this creates an owned instance just for comparison",
93         |diag| {
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");
97                 return;
98             }
99
100             let arg_snip = snippet(cx, arg_span, "..");
101             let expr_snip;
102             let eq_impl;
103             if with_deref.is_implemented() {
104                 expr_snip = format!("*{arg_snip}");
105                 eq_impl = with_deref;
106             } else {
107                 expr_snip = arg_snip.to_string();
108                 eq_impl = without_deref;
109             };
110
111             let span;
112             let hint;
113             if (eq_impl.ty_eq_other && left) || (eq_impl.other_eq_ty && !left) {
114                 span = expr.span;
115                 hint = expr_snip;
116             } else {
117                 span = expr.span.to(other.span);
118
119                 let cmp_span = if other.span < expr.span {
120                     other.span.between(expr.span)
121                 } else {
122                     expr.span.between(other.span)
123                 };
124                 if eq_impl.ty_eq_other {
125                     hint = format!(
126                         "{expr_snip}{}{}",
127                         snippet(cx, cmp_span, ".."),
128                         snippet(cx, other.span, "..")
129                     );
130                 } else {
131                     hint = format!(
132                         "{}{}{expr_snip}",
133                         snippet(cx, other.span, ".."),
134                         snippet(cx, cmp_span, "..")
135                     );
136                 }
137             }
138
139             diag.span_suggestion(
140                 span,
141                 "try",
142                 hint,
143                 Applicability::MachineApplicable, // snippet
144             );
145         },
146     );
147 }