Skip to content

Commit 71c9fdf

Browse files
nahuakangY-Nak
authored andcommitted
Refactor check_for_loop_range into its module
1 parent 6e4663d commit 71c9fdf

File tree

2 files changed

+396
-381
lines changed

2 files changed

+396
-381
lines changed
Lines changed: 390 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,390 @@
1+
use crate::utils::visitors::LocalUsedVisitor;
2+
use crate::utils::{
3+
contains_name, has_iter_method, higher, is_integer_const, match_trait_method, multispan_sugg, path_to_local_id,
4+
paths, snippet, span_lint_and_then, sugg, SpanlessEq,
5+
};
6+
use if_chain::if_chain;
7+
use rustc_ast::ast;
8+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
9+
use rustc_hir::def::{DefKind, Res};
10+
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
11+
use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, Mutability, Pat, PatKind, QPath};
12+
use rustc_lint::LateContext;
13+
use rustc_middle::hir::map::Map;
14+
use rustc_middle::middle::region;
15+
use rustc_middle::ty::{self, Ty};
16+
use rustc_span::symbol::{sym, Symbol};
17+
use std::iter::Iterator;
18+
use std::mem;
19+
20+
/// Checks for looping over a range and then indexing a sequence with it.
21+
/// The iteratee must be a range literal.
22+
#[allow(clippy::too_many_lines)]
23+
pub(super) fn check_for_loop_range<'tcx>(
24+
cx: &LateContext<'tcx>,
25+
pat: &'tcx Pat<'_>,
26+
arg: &'tcx Expr<'_>,
27+
body: &'tcx Expr<'_>,
28+
expr: &'tcx Expr<'_>,
29+
) {
30+
if let Some(higher::Range {
31+
start: Some(start),
32+
ref end,
33+
limits,
34+
}) = higher::range(arg)
35+
{
36+
// the var must be a single name
37+
if let PatKind::Binding(_, canonical_id, ident, _) = pat.kind {
38+
let mut visitor = VarVisitor {
39+
cx,
40+
var: canonical_id,
41+
indexed_mut: FxHashSet::default(),
42+
indexed_indirectly: FxHashMap::default(),
43+
indexed_directly: FxHashMap::default(),
44+
referenced: FxHashSet::default(),
45+
nonindex: false,
46+
prefer_mutable: false,
47+
};
48+
walk_expr(&mut visitor, body);
49+
50+
// linting condition: we only indexed one variable, and indexed it directly
51+
if visitor.indexed_indirectly.is_empty() && visitor.indexed_directly.len() == 1 {
52+
let (indexed, (indexed_extent, indexed_ty)) = visitor
53+
.indexed_directly
54+
.into_iter()
55+
.next()
56+
.expect("already checked that we have exactly 1 element");
57+
58+
// ensure that the indexed variable was declared before the loop, see #601
59+
if let Some(indexed_extent) = indexed_extent {
60+
let parent_id = cx.tcx.hir().get_parent_item(expr.hir_id);
61+
let parent_def_id = cx.tcx.hir().local_def_id(parent_id);
62+
let region_scope_tree = cx.tcx.region_scope_tree(parent_def_id);
63+
let pat_extent = region_scope_tree.var_scope(pat.hir_id.local_id);
64+
if region_scope_tree.is_subscope_of(indexed_extent, pat_extent) {
65+
return;
66+
}
67+
}
68+
69+
// don't lint if the container that is indexed does not have .iter() method
70+
let has_iter = has_iter_method(cx, indexed_ty);
71+
if has_iter.is_none() {
72+
return;
73+
}
74+
75+
// don't lint if the container that is indexed into is also used without
76+
// indexing
77+
if visitor.referenced.contains(&indexed) {
78+
return;
79+
}
80+
81+
let starts_at_zero = is_integer_const(cx, start, 0);
82+
83+
let skip = if starts_at_zero {
84+
String::new()
85+
} else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, start) {
86+
return;
87+
} else {
88+
format!(".skip({})", snippet(cx, start.span, ".."))
89+
};
90+
91+
let mut end_is_start_plus_val = false;
92+
93+
let take = if let Some(end) = *end {
94+
let mut take_expr = end;
95+
96+
if let ExprKind::Binary(ref op, ref left, ref right) = end.kind {
97+
if let BinOpKind::Add = op.node {
98+
let start_equal_left = SpanlessEq::new(cx).eq_expr(start, left);
99+
let start_equal_right = SpanlessEq::new(cx).eq_expr(start, right);
100+
101+
if start_equal_left {
102+
take_expr = right;
103+
} else if start_equal_right {
104+
take_expr = left;
105+
}
106+
107+
end_is_start_plus_val = start_equal_left | start_equal_right;
108+
}
109+
}
110+
111+
if is_len_call(end, indexed) || is_end_eq_array_len(cx, end, limits, indexed_ty) {
112+
String::new()
113+
} else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, take_expr) {
114+
return;
115+
} else {
116+
match limits {
117+
ast::RangeLimits::Closed => {
118+
let take_expr = sugg::Sugg::hir(cx, take_expr, "<count>");
119+
format!(".take({})", take_expr + sugg::ONE)
120+
},
121+
ast::RangeLimits::HalfOpen => format!(".take({})", snippet(cx, take_expr.span, "..")),
122+
}
123+
}
124+
} else {
125+
String::new()
126+
};
127+
128+
let (ref_mut, method) = if visitor.indexed_mut.contains(&indexed) {
129+
("mut ", "iter_mut")
130+
} else {
131+
("", "iter")
132+
};
133+
134+
let take_is_empty = take.is_empty();
135+
let mut method_1 = take;
136+
let mut method_2 = skip;
137+
138+
if end_is_start_plus_val {
139+
mem::swap(&mut method_1, &mut method_2);
140+
}
141+
142+
if visitor.nonindex {
143+
span_lint_and_then(
144+
cx,
145+
super::NEEDLESS_RANGE_LOOP,
146+
expr.span,
147+
&format!("the loop variable `{}` is used to index `{}`", ident.name, indexed),
148+
|diag| {
149+
multispan_sugg(
150+
diag,
151+
"consider using an iterator",
152+
vec![
153+
(pat.span, format!("({}, <item>)", ident.name)),
154+
(
155+
arg.span,
156+
format!("{}.{}().enumerate(){}{}", indexed, method, method_1, method_2),
157+
),
158+
],
159+
);
160+
},
161+
);
162+
} else {
163+
let repl = if starts_at_zero && take_is_empty {
164+
format!("&{}{}", ref_mut, indexed)
165+
} else {
166+
format!("{}.{}(){}{}", indexed, method, method_1, method_2)
167+
};
168+
169+
span_lint_and_then(
170+
cx,
171+
super::NEEDLESS_RANGE_LOOP,
172+
expr.span,
173+
&format!("the loop variable `{}` is only used to index `{}`", ident.name, indexed),
174+
|diag| {
175+
multispan_sugg(
176+
diag,
177+
"consider using an iterator",
178+
vec![(pat.span, "<item>".to_string()), (arg.span, repl)],
179+
);
180+
},
181+
);
182+
}
183+
}
184+
}
185+
}
186+
}
187+
188+
fn is_len_call(expr: &Expr<'_>, var: Symbol) -> bool {
189+
if_chain! {
190+
if let ExprKind::MethodCall(ref method, _, ref len_args, _) = expr.kind;
191+
if len_args.len() == 1;
192+
if method.ident.name == sym!(len);
193+
if let ExprKind::Path(QPath::Resolved(_, ref path)) = len_args[0].kind;
194+
if path.segments.len() == 1;
195+
if path.segments[0].ident.name == var;
196+
then {
197+
return true;
198+
}
199+
}
200+
201+
false
202+
}
203+
204+
fn is_end_eq_array_len<'tcx>(
205+
cx: &LateContext<'tcx>,
206+
end: &Expr<'_>,
207+
limits: ast::RangeLimits,
208+
indexed_ty: Ty<'tcx>,
209+
) -> bool {
210+
if_chain! {
211+
if let ExprKind::Lit(ref lit) = end.kind;
212+
if let ast::LitKind::Int(end_int, _) = lit.node;
213+
if let ty::Array(_, arr_len_const) = indexed_ty.kind();
214+
if let Some(arr_len) = arr_len_const.try_eval_usize(cx.tcx, cx.param_env);
215+
then {
216+
return match limits {
217+
ast::RangeLimits::Closed => end_int + 1 >= arr_len.into(),
218+
ast::RangeLimits::HalfOpen => end_int >= arr_len.into(),
219+
};
220+
}
221+
}
222+
223+
false
224+
}
225+
226+
struct VarVisitor<'a, 'tcx> {
227+
/// context reference
228+
cx: &'a LateContext<'tcx>,
229+
/// var name to look for as index
230+
var: HirId,
231+
/// indexed variables that are used mutably
232+
indexed_mut: FxHashSet<Symbol>,
233+
/// indirectly indexed variables (`v[(i + 4) % N]`), the extend is `None` for global
234+
indexed_indirectly: FxHashMap<Symbol, Option<region::Scope>>,
235+
/// subset of `indexed` of vars that are indexed directly: `v[i]`
236+
/// this will not contain cases like `v[calc_index(i)]` or `v[(i + 4) % N]`
237+
indexed_directly: FxHashMap<Symbol, (Option<region::Scope>, Ty<'tcx>)>,
238+
/// Any names that are used outside an index operation.
239+
/// Used to detect things like `&mut vec` used together with `vec[i]`
240+
referenced: FxHashSet<Symbol>,
241+
/// has the loop variable been used in expressions other than the index of
242+
/// an index op?
243+
nonindex: bool,
244+
/// Whether we are inside the `$` in `&mut $` or `$ = foo` or `$.bar`, where bar
245+
/// takes `&mut self`
246+
prefer_mutable: bool,
247+
}
248+
249+
impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
250+
fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool {
251+
if_chain! {
252+
// the indexed container is referenced by a name
253+
if let ExprKind::Path(ref seqpath) = seqexpr.kind;
254+
if let QPath::Resolved(None, ref seqvar) = *seqpath;
255+
if seqvar.segments.len() == 1;
256+
then {
257+
let index_used_directly = path_to_local_id(idx, self.var);
258+
let indexed_indirectly = {
259+
let mut used_visitor = LocalUsedVisitor::new(self.var);
260+
walk_expr(&mut used_visitor, idx);
261+
used_visitor.used
262+
};
263+
264+
if indexed_indirectly || index_used_directly {
265+
if self.prefer_mutable {
266+
self.indexed_mut.insert(seqvar.segments[0].ident.name);
267+
}
268+
let res = self.cx.qpath_res(seqpath, seqexpr.hir_id);
269+
match res {
270+
Res::Local(hir_id) => {
271+
let parent_id = self.cx.tcx.hir().get_parent_item(expr.hir_id);
272+
let parent_def_id = self.cx.tcx.hir().local_def_id(parent_id);
273+
let extent = self.cx.tcx.region_scope_tree(parent_def_id).var_scope(hir_id.local_id);
274+
if indexed_indirectly {
275+
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent));
276+
}
277+
if index_used_directly {
278+
self.indexed_directly.insert(
279+
seqvar.segments[0].ident.name,
280+
(Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)),
281+
);
282+
}
283+
return false; // no need to walk further *on the variable*
284+
}
285+
Res::Def(DefKind::Static | DefKind::Const, ..) => {
286+
if indexed_indirectly {
287+
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
288+
}
289+
if index_used_directly {
290+
self.indexed_directly.insert(
291+
seqvar.segments[0].ident.name,
292+
(None, self.cx.typeck_results().node_type(seqexpr.hir_id)),
293+
);
294+
}
295+
return false; // no need to walk further *on the variable*
296+
}
297+
_ => (),
298+
}
299+
}
300+
}
301+
}
302+
true
303+
}
304+
}
305+
306+
impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> {
307+
type Map = Map<'tcx>;
308+
309+
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
310+
if_chain! {
311+
// a range index op
312+
if let ExprKind::MethodCall(ref meth, _, ref args, _) = expr.kind;
313+
if (meth.ident.name == sym::index && match_trait_method(self.cx, expr, &paths::INDEX))
314+
|| (meth.ident.name == sym::index_mut && match_trait_method(self.cx, expr, &paths::INDEX_MUT));
315+
if !self.check(&args[1], &args[0], expr);
316+
then { return }
317+
}
318+
319+
if_chain! {
320+
// an index op
321+
if let ExprKind::Index(ref seqexpr, ref idx) = expr.kind;
322+
if !self.check(idx, seqexpr, expr);
323+
then { return }
324+
}
325+
326+
if_chain! {
327+
// directly using a variable
328+
if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind;
329+
if let Res::Local(local_id) = path.res;
330+
then {
331+
if local_id == self.var {
332+
self.nonindex = true;
333+
} else {
334+
// not the correct variable, but still a variable
335+
self.referenced.insert(path.segments[0].ident.name);
336+
}
337+
}
338+
}
339+
340+
let old = self.prefer_mutable;
341+
match expr.kind {
342+
ExprKind::AssignOp(_, ref lhs, ref rhs) | ExprKind::Assign(ref lhs, ref rhs, _) => {
343+
self.prefer_mutable = true;
344+
self.visit_expr(lhs);
345+
self.prefer_mutable = false;
346+
self.visit_expr(rhs);
347+
},
348+
ExprKind::AddrOf(BorrowKind::Ref, mutbl, ref expr) => {
349+
if mutbl == Mutability::Mut {
350+
self.prefer_mutable = true;
351+
}
352+
self.visit_expr(expr);
353+
},
354+
ExprKind::Call(ref f, args) => {
355+
self.visit_expr(f);
356+
for expr in args {
357+
let ty = self.cx.typeck_results().expr_ty_adjusted(expr);
358+
self.prefer_mutable = false;
359+
if let ty::Ref(_, _, mutbl) = *ty.kind() {
360+
if mutbl == Mutability::Mut {
361+
self.prefer_mutable = true;
362+
}
363+
}
364+
self.visit_expr(expr);
365+
}
366+
},
367+
ExprKind::MethodCall(_, _, args, _) => {
368+
let def_id = self.cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap();
369+
for (ty, expr) in self.cx.tcx.fn_sig(def_id).inputs().skip_binder().iter().zip(args) {
370+
self.prefer_mutable = false;
371+
if let ty::Ref(_, _, mutbl) = *ty.kind() {
372+
if mutbl == Mutability::Mut {
373+
self.prefer_mutable = true;
374+
}
375+
}
376+
self.visit_expr(expr);
377+
}
378+
},
379+
ExprKind::Closure(_, _, body_id, ..) => {
380+
let body = self.cx.tcx.hir().body(body_id);
381+
self.visit_expr(&body.value);
382+
},
383+
_ => walk_expr(self, expr),
384+
}
385+
self.prefer_mutable = old;
386+
}
387+
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
388+
NestedVisitorMap::None
389+
}
390+
}

0 commit comments

Comments
 (0)