-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
Copy pathmanual_contains.rs
113 lines (105 loc) · 3.94 KB
/
manual_contains.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::eager_or_lazy::switch_to_eager_eval;
use clippy_utils::peel_hir_pat_refs;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use rustc_ast::UnOp;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::{BinOpKind, Body, Expr, ExprKind, HirId, QPath};
use rustc_lint::LateContext;
use rustc_middle::ty::{self};
use rustc_span::source_map::Spanned;
use super::MANUAL_CONTAINS;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, closure_arg: &Expr<'_>) {
let mut app = Applicability::MachineApplicable;
if !expr.span.from_expansion()
// check if `iter().any()` can be replaced with `contains()`
&& let ExprKind::Closure(closure) = closure_arg.kind
&& let Body{params: [param],value} = cx.tcx.hir_body(closure.body)
&& let ExprKind::Binary(op, lhs, rhs) = value.kind
&& let (peeled_ref_pat, _) = peel_hir_pat_refs(param.pat)
&& let Some((snip,snip_expr)) = can_replace_with_contains(cx, op, lhs, rhs, peeled_ref_pat.hir_id, &mut app)
&& let ref_type = cx.typeck_results().expr_ty_adjusted(recv)
&& let ty::Ref(_, inner_type, _) = ref_type.kind()
&& let ty::Slice(slice_type) = inner_type.kind()
&& *slice_type == cx.typeck_results().expr_ty(snip_expr)
{
span_lint_and_sugg(
cx,
MANUAL_CONTAINS,
expr.span,
"using `contains()` instead of `iter().any()` is more efficient",
"try",
format!(
"{}.contains({})",
snippet_with_applicability(cx, recv.span, "_", &mut app),
snip
),
app,
);
}
}
enum EligibleArg {
IsClosureArg,
ContainsArg(String),
}
fn try_get_eligible_arg<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
closure_arg_id: HirId,
applicability: &mut Applicability,
) -> Option<(EligibleArg, &'tcx Expr<'tcx>)> {
let mut get_snippet = |expr: &Expr<'_>, needs_borrow: bool| {
let sugg = Sugg::hir_with_applicability(cx, expr, "_", applicability);
EligibleArg::ContainsArg((if needs_borrow { sugg.addr() } else { sugg }).to_string())
};
match expr.kind {
ExprKind::Path(QPath::Resolved(_, path)) => {
if path.res == Res::Local(closure_arg_id) {
Some((EligibleArg::IsClosureArg, expr))
} else {
Some((get_snippet(expr, true), expr))
}
},
ExprKind::Unary(UnOp::Deref, inner) => {
if let ExprKind::Path(QPath::Resolved(_, path)) = inner.kind {
if path.res == Res::Local(closure_arg_id) {
Some((EligibleArg::IsClosureArg, expr))
} else {
Some((get_snippet(inner, false), expr))
}
} else {
None
}
},
_ => {
if switch_to_eager_eval(cx, expr) {
Some((get_snippet(expr, true), expr))
} else {
None
}
},
}
}
fn can_replace_with_contains<'tcx>(
cx: &LateContext<'tcx>,
bin_op: Spanned<BinOpKind>,
left_expr: &'tcx Expr<'tcx>,
right_expr: &'tcx Expr<'tcx>,
closure_arg_id: HirId,
applicability: &mut Applicability,
) -> Option<(String, &'tcx Expr<'tcx>)> {
if bin_op.node != BinOpKind::Eq {
return None;
}
let left_candidate = try_get_eligible_arg(cx, left_expr, closure_arg_id, applicability)?;
let right_candidate = try_get_eligible_arg(cx, right_expr, closure_arg_id, applicability)?;
match (left_candidate, right_candidate) {
((EligibleArg::IsClosureArg, _), (EligibleArg::ContainsArg(snip), candidate_expr))
| ((EligibleArg::ContainsArg(snip), candidate_expr), (EligibleArg::IsClosureArg, _)) => {
Some((snip, candidate_expr))
},
_ => None,
}
}