1 use super::NEEDLESS_RANGE_LOOP
;
2 use clippy_utils
::diagnostics
::{multispan_sugg, span_lint_and_then}
;
3 use clippy_utils
::source
::snippet
;
4 use clippy_utils
::ty
::has_iter_method
;
5 use clippy_utils
::visitors
::is_local_used
;
6 use clippy_utils
::{contains_name, higher, is_integer_const, match_trait_method, paths, sugg, SpanlessEq}
;
7 use if_chain
::if_chain
;
9 use rustc_data_structures
::fx
::{FxHashMap, FxHashSet}
;
10 use rustc_hir
::def
::{DefKind, Res}
;
11 use rustc_hir
::intravisit
::{walk_expr, Visitor}
;
12 use rustc_hir
::{BinOpKind, BorrowKind, Closure, Expr, ExprKind, HirId, Mutability, Pat, PatKind, QPath}
;
13 use rustc_lint
::LateContext
;
14 use rustc_middle
::middle
::region
;
15 use rustc_middle
::ty
::{self, Ty}
;
16 use rustc_span
::symbol
::{sym, Symbol}
;
17 use std
::iter
::{self, Iterator}
;
20 /// Checks for looping over a range and then indexing a sequence with it.
21 /// The iteratee must be a range literal.
22 #[expect(clippy::too_many_lines)]
23 pub(super) fn check
<'tcx
>(
24 cx
: &LateContext
<'tcx
>,
30 if let Some(higher
::Range
{
34 }) = higher
::Range
::hir(arg
)
36 // the var must be a single name
37 if let PatKind
::Binding(_
, canonical_id
, ident
, _
) = pat
.kind
{
38 let mut visitor
= VarVisitor
{
41 indexed_mut
: FxHashSet
::default(),
42 indexed_indirectly
: FxHashMap
::default(),
43 indexed_directly
: FxHashMap
::default(),
44 referenced
: FxHashSet
::default(),
46 prefer_mutable
: false,
48 walk_expr(&mut visitor
, body
);
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
56 .expect("already checked that we have exactly 1 element");
58 // ensure that the indexed variable was declared before the loop, see #601
59 if let Some(indexed_extent
) = indexed_extent
{
60 let parent_def_id
= cx
.tcx
.hir().get_parent_item(expr
.hir_id
);
61 let region_scope_tree
= cx
.tcx
.region_scope_tree(parent_def_id
);
62 let pat_extent
= region_scope_tree
.var_scope(pat
.hir_id
.local_id
).unwrap();
63 if region_scope_tree
.is_subscope_of(indexed_extent
, pat_extent
) {
68 // don't lint if the container that is indexed does not have .iter() method
69 let has_iter
= has_iter_method(cx
, indexed_ty
);
70 if has_iter
.is_none() {
74 // don't lint if the container that is indexed into is also used without
76 if visitor
.referenced
.contains(&indexed
) {
80 let starts_at_zero
= is_integer_const(cx
, start
, 0);
82 let skip
= if starts_at_zero
{
84 } else if visitor
.indexed_mut
.contains(&indexed
) && contains_name(indexed
, start
) {
87 format
!(".skip({})", snippet(cx
, start
.span
, ".."))
90 let mut end_is_start_plus_val
= false;
92 let take
= if let Some(end
) = *end
{
93 let mut take_expr
= end
;
95 if let ExprKind
::Binary(ref op
, left
, right
) = end
.kind
{
96 if op
.node
== BinOpKind
::Add
{
97 let start_equal_left
= SpanlessEq
::new(cx
).eq_expr(start
, left
);
98 let start_equal_right
= SpanlessEq
::new(cx
).eq_expr(start
, right
);
100 if start_equal_left
{
102 } else if start_equal_right
{
106 end_is_start_plus_val
= start_equal_left
| start_equal_right
;
110 if is_len_call(end
, indexed
) || is_end_eq_array_len(cx
, end
, limits
, indexed_ty
) {
112 } else if visitor
.indexed_mut
.contains(&indexed
) && contains_name(indexed
, take_expr
) {
116 ast
::RangeLimits
::Closed
=> {
117 let take_expr
= sugg
::Sugg
::hir(cx
, take_expr
, "<count>");
118 format
!(".take({})", take_expr
+ sugg
::ONE
)
120 ast
::RangeLimits
::HalfOpen
=> {
121 format
!(".take({})", snippet(cx
, take_expr
.span
, ".."))
129 let (ref_mut
, method
) = if visitor
.indexed_mut
.contains(&indexed
) {
135 let take_is_empty
= take
.is_empty();
136 let mut method_1
= take
;
137 let mut method_2
= skip
;
139 if end_is_start_plus_val
{
140 mem
::swap(&mut method_1
, &mut method_2
);
143 if visitor
.nonindex
{
148 &format
!("the loop variable `{}` is used to index `{}`", ident
.name
, indexed
),
152 "consider using an iterator",
154 (pat
.span
, format
!("({}, <item>)", ident
.name
)),
157 format
!("{}.{}().enumerate(){}{}", indexed
, method
, method_1
, method_2
),
164 let repl
= if starts_at_zero
&& take_is_empty
{
165 format
!("&{}{}", ref_mut
, indexed
)
167 format
!("{}.{}(){}{}", indexed
, method
, method_1
, method_2
)
174 &format
!("the loop variable `{}` is only used to index `{}`", ident
.name
, indexed
),
178 "consider using an iterator",
179 vec
![(pat
.span
, "<item>".to_string()), (arg
.span
, repl
)],
189 fn is_len_call(expr
: &Expr
<'_
>, var
: Symbol
) -> bool
{
191 if let ExprKind
::MethodCall(method
, recv
, [], _
) = expr
.kind
;
192 if method
.ident
.name
== sym
::len
;
193 if let ExprKind
::Path(QPath
::Resolved(_
, path
)) = recv
.kind
;
194 if path
.segments
.len() == 1;
195 if path
.segments
[0].ident
.name
== var
;
204 fn is_end_eq_array_len
<'tcx
>(
205 cx
: &LateContext
<'tcx
>,
207 limits
: ast
::RangeLimits
,
208 indexed_ty
: Ty
<'tcx
>,
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
);
216 return match limits
{
217 ast
::RangeLimits
::Closed
=> end_int
+ 1 >= arr_len
.into(),
218 ast
::RangeLimits
::HalfOpen
=> end_int
>= arr_len
.into(),
226 struct VarVisitor
<'a
, 'tcx
> {
227 /// context reference
228 cx
: &'a LateContext
<'tcx
>,
229 /// var name to look for as index
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
244 /// Whether we are inside the `$` in `&mut $` or `$ = foo` or `$.bar`, where bar
245 /// takes `&mut self`
246 prefer_mutable
: bool
,
249 impl<'a
, 'tcx
> VarVisitor
<'a
, 'tcx
> {
250 fn check(&mut self, idx
: &'tcx Expr
<'_
>, seqexpr
: &'tcx Expr
<'_
>, expr
: &'tcx Expr
<'_
>) -> bool
{
252 // the indexed container is referenced by a name
253 if let ExprKind
::Path(ref seqpath
) = seqexpr
.kind
;
254 if let QPath
::Resolved(None
, seqvar
) = *seqpath
;
255 if seqvar
.segments
.len() == 1;
256 if is_local_used(self.cx
, idx
, self.var
);
258 if self.prefer_mutable
{
259 self.indexed_mut
.insert(seqvar
.segments
[0].ident
.name
);
261 let index_used_directly
= matches
!(idx
.kind
, ExprKind
::Path(_
));
262 let res
= self.cx
.qpath_res(seqpath
, seqexpr
.hir_id
);
264 Res
::Local(hir_id
) => {
265 let parent_def_id
= self.cx
.tcx
.hir().get_parent_item(expr
.hir_id
);
268 .region_scope_tree(parent_def_id
)
269 .var_scope(hir_id
.local_id
)
271 if index_used_directly
{
272 self.indexed_directly
.insert(
273 seqvar
.segments
[0].ident
.name
,
274 (Some(extent
), self.cx
.typeck_results().node_type(seqexpr
.hir_id
)),
277 self.indexed_indirectly
.insert(seqvar
.segments
[0].ident
.name
, Some(extent
));
279 return false; // no need to walk further *on the variable*
281 Res
::Def(DefKind
::Static (_
)| DefKind
::Const
, ..) => {
282 if index_used_directly
{
283 self.indexed_directly
.insert(
284 seqvar
.segments
[0].ident
.name
,
285 (None
, self.cx
.typeck_results().node_type(seqexpr
.hir_id
)),
288 self.indexed_indirectly
.insert(seqvar
.segments
[0].ident
.name
, None
);
290 return false; // no need to walk further *on the variable*
300 impl<'a
, 'tcx
> Visitor
<'tcx
> for VarVisitor
<'a
, 'tcx
> {
301 fn visit_expr(&mut self, expr
: &'tcx Expr
<'_
>) {
304 if let ExprKind
::MethodCall(meth
, args_0
, [args_1
, ..], _
) = &expr
.kind
;
305 if (meth
.ident
.name
== sym
::index
&& match_trait_method(self.cx
, expr
, &paths
::INDEX
))
306 || (meth
.ident
.name
== sym
::index_mut
&& match_trait_method(self.cx
, expr
, &paths
::INDEX_MUT
));
307 if !self.check(args_1
, args_0
, expr
);
313 if let ExprKind
::Index(seqexpr
, idx
) = expr
.kind
;
314 if !self.check(idx
, seqexpr
, expr
);
319 // directly using a variable
320 if let ExprKind
::Path(QPath
::Resolved(None
, path
)) = expr
.kind
;
321 if let Res
::Local(local_id
) = path
.res
;
323 if local_id
== self.var
{
324 self.nonindex
= true;
326 // not the correct variable, but still a variable
327 self.referenced
.insert(path
.segments
[0].ident
.name
);
332 let old
= self.prefer_mutable
;
334 ExprKind
::AssignOp(_
, lhs
, rhs
) | ExprKind
::Assign(lhs
, rhs
, _
) => {
335 self.prefer_mutable
= true;
336 self.visit_expr(lhs
);
337 self.prefer_mutable
= false;
338 self.visit_expr(rhs
);
340 ExprKind
::AddrOf(BorrowKind
::Ref
, mutbl
, expr
) => {
341 if mutbl
== Mutability
::Mut
{
342 self.prefer_mutable
= true;
344 self.visit_expr(expr
);
346 ExprKind
::Call(f
, args
) => {
349 let ty
= self.cx
.typeck_results().expr_ty_adjusted(expr
);
350 self.prefer_mutable
= false;
351 if let ty
::Ref(_
, _
, mutbl
) = *ty
.kind() {
352 if mutbl
== Mutability
::Mut
{
353 self.prefer_mutable
= true;
356 self.visit_expr(expr
);
359 ExprKind
::MethodCall(_
, receiver
, args
, _
) => {
360 let def_id
= self.cx
.typeck_results().type_dependent_def_id(expr
.hir_id
).unwrap();
361 for (ty
, expr
) in iter
::zip(
362 self.cx
.tcx
.fn_sig(def_id
).inputs().skip_binder(),
363 std
::iter
::once(receiver
).chain(args
.iter()),
365 self.prefer_mutable
= false;
366 if let ty
::Ref(_
, _
, mutbl
) = *ty
.kind() {
367 if mutbl
== Mutability
::Mut
{
368 self.prefer_mutable
= true;
371 self.visit_expr(expr
);
374 ExprKind
::Closure(&Closure { body, .. }
) => {
375 let body
= self.cx
.tcx
.hir().body(body
);
376 self.visit_expr(body
.value
);
378 _
=> walk_expr(self, expr
),
380 self.prefer_mutable
= old
;