Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit b6fa4d4

Browse files
committed
Extend explicit_iter_loop to all types
1 parent 476efe9 commit b6fa4d4

File tree

8 files changed

+460
-86
lines changed

8 files changed

+460
-86
lines changed

clippy_lints/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
679679
});
680680
store.register_late_pass(|_| Box::<shadow::Shadow>::default());
681681
store.register_late_pass(|_| Box::new(unit_types::UnitTypes));
682-
store.register_late_pass(|_| Box::new(loops::Loops));
682+
store.register_late_pass(move |_| Box::new(loops::Loops::new(msrv())));
683683
store.register_late_pass(|_| Box::<main_recursion::MainRecursion>::default());
684684
store.register_late_pass(|_| Box::new(lifetimes::Lifetimes));
685685
store.register_late_pass(|_| Box::new(entry::HashMapPass));
Lines changed: 162 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,37 @@
11
use super::EXPLICIT_ITER_LOOP;
22
use clippy_utils::diagnostics::span_lint_and_sugg;
33
use clippy_utils::is_trait_method;
4+
use clippy_utils::msrvs::{self, Msrv};
45
use clippy_utils::source::snippet_with_applicability;
5-
use clippy_utils::ty::is_type_diagnostic_item;
6+
use clippy_utils::ty::{implements_trait_with_env, make_normalized_projection_with_regions, normalize_with_regions,
7+
make_normalized_projection, implements_trait, is_copy};
68
use rustc_errors::Applicability;
79
use rustc_hir::{Expr, Mutability};
810
use rustc_lint::LateContext;
9-
use rustc_middle::ty::{self, Ty};
11+
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
12+
use rustc_middle::ty::{self, EarlyBinder, TypeAndMut, Ty};
1013
use rustc_span::sym;
1114

12-
pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, arg: &Expr<'_>, method_name: &str) {
13-
let should_lint = match method_name {
14-
"iter" | "iter_mut" => is_ref_iterable_type(cx, self_arg),
15-
"into_iter" if is_trait_method(cx, arg, sym::IntoIterator) => {
15+
pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, call_expr: &Expr<'_>, method_name: &str, msrv: &Msrv) {
16+
let borrow_kind = match method_name {
17+
"iter" | "iter_mut" => match is_ref_iterable(cx, self_arg, call_expr) {
18+
Some((kind, ty)) => {
19+
if let ty::Array(_, count) = *ty.peel_refs().kind() {
20+
if !ty.is_ref() {
21+
if !msrv.meets(msrvs::ARRAY_INTO_ITERATOR) {
22+
return;
23+
}
24+
} else if count.try_eval_target_usize(cx.tcx, cx.param_env).map_or(true, |x| x > 32)
25+
&& !msrv.meets(msrvs::ARRAY_IMPL_ANY_LEN)
26+
{
27+
return
28+
}
29+
}
30+
kind
31+
},
32+
None => return,
33+
},
34+
"into_iter" if is_trait_method(cx, call_expr, sym::IntoIterator) => {
1635
let receiver_ty = cx.typeck_results().expr_ty(self_arg);
1736
let receiver_ty_adjusted = cx.typeck_results().expr_ty_adjusted(self_arg);
1837
let ref_receiver_ty = cx.tcx.mk_ref(
@@ -22,54 +41,159 @@ pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, arg: &Expr<'_>, m
2241
mutbl: Mutability::Not,
2342
},
2443
);
25-
receiver_ty_adjusted == ref_receiver_ty
44+
if receiver_ty_adjusted == ref_receiver_ty {
45+
AdjustKind::None
46+
} else {
47+
return;
48+
}
2649
},
27-
_ => false,
50+
_ => return,
2851
};
2952

30-
if !should_lint {
31-
return;
32-
}
33-
3453
let mut applicability = Applicability::MachineApplicable;
3554
let object = snippet_with_applicability(cx, self_arg.span, "_", &mut applicability);
36-
let muta = if method_name == "iter_mut" { "mut " } else { "" };
55+
let prefix = match borrow_kind {
56+
AdjustKind::None => "",
57+
AdjustKind::Borrow => "&",
58+
AdjustKind::BorrowMut => "&mut ",
59+
AdjustKind::Deref => "*",
60+
AdjustKind::Reborrow => "&*",
61+
AdjustKind::ReborrowMut => "&mut *",
62+
};
3763
span_lint_and_sugg(
3864
cx,
3965
EXPLICIT_ITER_LOOP,
40-
arg.span,
66+
call_expr.span,
4167
"it is more concise to loop over references to containers instead of using explicit \
4268
iteration methods",
4369
"to write this more concisely, try",
44-
format!("&{muta}{object}"),
70+
format!("{prefix}{object}"),
4571
applicability,
4672
);
4773
}
4874

49-
/// Returns `true` if the type of expr is one that provides `IntoIterator` impls
50-
/// for `&T` and `&mut T`, such as `Vec`.
51-
#[rustfmt::skip]
52-
fn is_ref_iterable_type(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
53-
// no walk_ptrs_ty: calling iter() on a reference can make sense because it
54-
// will allow further borrows afterwards
55-
let ty = cx.typeck_results().expr_ty(e);
56-
is_iterable_array(ty, cx) ||
57-
is_type_diagnostic_item(cx, ty, sym::Vec) ||
58-
is_type_diagnostic_item(cx, ty, sym::LinkedList) ||
59-
is_type_diagnostic_item(cx, ty, sym::HashMap) ||
60-
is_type_diagnostic_item(cx, ty, sym::HashSet) ||
61-
is_type_diagnostic_item(cx, ty, sym::VecDeque) ||
62-
is_type_diagnostic_item(cx, ty, sym::BinaryHeap) ||
63-
is_type_diagnostic_item(cx, ty, sym::BTreeMap) ||
64-
is_type_diagnostic_item(cx, ty, sym::BTreeSet)
75+
enum AdjustKind {
76+
None,
77+
Borrow,
78+
BorrowMut,
79+
Deref,
80+
Reborrow,
81+
ReborrowMut,
82+
}
83+
impl AdjustKind {
84+
fn borrow(mutbl: Mutability) -> Self {
85+
match mutbl {
86+
Mutability::Not => Self::Borrow,
87+
Mutability::Mut => Self::BorrowMut,
88+
}
89+
}
90+
91+
fn auto_borrow(mutbl: AutoBorrowMutability) -> Self {
92+
match mutbl {
93+
AutoBorrowMutability::Not => Self::Borrow,
94+
AutoBorrowMutability::Mut { .. } => Self::BorrowMut,
95+
}
96+
}
97+
98+
fn reborrow(mutbl: AutoBorrowMutability) -> Self {
99+
match mutbl {
100+
AutoBorrowMutability::Not => Self::Reborrow,
101+
AutoBorrowMutability::Mut { .. } => Self::ReborrowMut,
102+
}
103+
}
65104
}
66105

67-
fn is_iterable_array<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool {
68-
// IntoIterator is currently only implemented for array sizes <= 32 in rustc
69-
match ty.kind() {
70-
ty::Array(_, n) => n
71-
.try_eval_target_usize(cx.tcx, cx.param_env)
72-
.map_or(false, |val| (0..=32).contains(&val)),
73-
_ => false,
106+
/// Checks if an `iter` or `iter_mut` call returns `IntoIterator::IntoIter`. Returns how the
107+
/// argument needs to be adjusted.
108+
fn is_ref_iterable<'tcx>(cx: &LateContext<'tcx>, self_arg: &Expr<'_>, call_expr: &Expr<'_>) -> Option<(AdjustKind, Ty<'tcx>)> {
109+
let typeck = cx.typeck_results();
110+
if let Some(trait_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator)
111+
&& let Some(fn_id) = typeck.type_dependent_def_id(call_expr.hir_id)
112+
&& let sig = cx.tcx.liberate_late_bound_regions(fn_id, cx.tcx.fn_sig(fn_id).skip_binder())
113+
&& let &[req_self_ty, req_res_ty] = &**sig.inputs_and_output
114+
&& let param_env = cx.tcx.param_env(fn_id)
115+
&& implements_trait_with_env(cx.tcx, param_env, req_self_ty, trait_id, [])
116+
&& let Some(into_iter_ty) =
117+
make_normalized_projection_with_regions(cx.tcx, param_env, trait_id, sym!(IntoIter), [req_self_ty])
118+
&& let req_res_ty = normalize_with_regions(cx.tcx, param_env, req_res_ty)
119+
&& into_iter_ty == req_res_ty
120+
{
121+
let adjustments = typeck.expr_adjustments(self_arg);
122+
let self_ty = typeck.expr_ty(self_arg);
123+
let self_is_copy = is_copy(cx, self_ty);
124+
125+
if adjustments.is_empty() && self_is_copy {
126+
return Some((AdjustKind::None, self_ty));
127+
}
128+
129+
let res_ty = cx.tcx.erase_regions(EarlyBinder::bind(req_res_ty).subst(cx.tcx, typeck.node_substs(call_expr.hir_id)));
130+
if !adjustments.is_empty() && self_is_copy {
131+
if implements_trait(cx, self_ty, trait_id, &[])
132+
&& let Some(ty) = make_normalized_projection(cx.tcx, cx.param_env, trait_id, sym!(IntoIter), [self_ty])
133+
&& ty == res_ty
134+
{
135+
return Some((AdjustKind::None, self_ty));
136+
}
137+
}
138+
139+
let mutbl = if let ty::Ref(_, _, mutbl) = *req_self_ty.kind() {
140+
Some(mutbl)
141+
} else {
142+
None
143+
};
144+
if let Some(mutbl) = mutbl
145+
&& !self_ty.is_ref()
146+
{
147+
let self_ty = cx.tcx.mk_ref(cx.tcx.lifetimes.re_erased, TypeAndMut {
148+
ty: self_ty,
149+
mutbl,
150+
});
151+
if implements_trait(cx, self_ty, trait_id, &[])
152+
&& let Some(ty) = make_normalized_projection(cx.tcx, cx.param_env, trait_id, sym!(IntoIter), [self_ty])
153+
&& ty == res_ty
154+
{
155+
return Some((AdjustKind::borrow(mutbl), self_ty));
156+
}
157+
}
158+
159+
match adjustments {
160+
[] => Some((AdjustKind::None, self_ty)),
161+
&[Adjustment { kind: Adjust::Deref(_), ..}, Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(_, mutbl)), target }, ..] => {
162+
if target != self_ty
163+
&& implements_trait(cx, target, trait_id, &[])
164+
&& let Some(ty) = make_normalized_projection(cx.tcx, cx.param_env, trait_id, sym!(IntoIter), [target])
165+
&& ty == res_ty
166+
{
167+
Some((AdjustKind::reborrow(mutbl), target))
168+
} else {
169+
None
170+
}
171+
}
172+
&[Adjustment { kind: Adjust::Deref(_), target }, ..] => {
173+
if is_copy(cx, target)
174+
&& implements_trait(cx, target, trait_id, &[])
175+
&& let Some(ty) = make_normalized_projection(cx.tcx, cx.param_env, trait_id, sym!(IntoIter), [target])
176+
&& ty == res_ty
177+
{
178+
Some((AdjustKind::Deref, target))
179+
} else {
180+
None
181+
}
182+
}
183+
&[Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(_, mutbl)), target }, ..] => {
184+
if self_ty.is_ref()
185+
&& implements_trait(cx, target, trait_id, &[])
186+
&& let Some(ty) = make_normalized_projection(cx.tcx, cx.param_env, trait_id, sym!(IntoIter), [target])
187+
&& ty == res_ty
188+
{
189+
Some((AdjustKind::auto_borrow(mutbl), target))
190+
} else {
191+
None
192+
}
193+
}
194+
_ => None,
195+
}
196+
} else {
197+
None
74198
}
75199
}

clippy_lints/src/loops/mod.rs

Lines changed: 51 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ mod while_let_loop;
2020
mod while_let_on_iterator;
2121

2222
use clippy_utils::higher;
23+
use clippy_utils::msrvs::Msrv;
2324
use rustc_hir::{Expr, ExprKind, LoopSource, Pat};
2425
use rustc_lint::{LateContext, LateLintPass};
25-
use rustc_session::{declare_lint_pass, declare_tool_lint};
26+
use rustc_session::{declare_tool_lint, impl_lint_pass};
2627
use rustc_span::source_map::Span;
2728
use utils::{make_iterator_snippet, IncrementVisitor, InitializeVisitor};
2829

@@ -606,7 +607,15 @@ declare_clippy_lint! {
606607
"checking for emptiness of a `Vec` in the loop condition and popping an element in the body"
607608
}
608609

609-
declare_lint_pass!(Loops => [
610+
pub struct Loops {
611+
msrv: Msrv,
612+
}
613+
impl Loops {
614+
pub fn new(msrv: Msrv) -> Self {
615+
Self { msrv }
616+
}
617+
}
618+
impl_lint_pass!(Loops => [
610619
MANUAL_MEMCPY,
611620
MANUAL_FLATTEN,
612621
NEEDLESS_RANGE_LOOP,
@@ -645,7 +654,7 @@ impl<'tcx> LateLintPass<'tcx> for Loops {
645654
if body.span.from_expansion() {
646655
return;
647656
}
648-
check_for_loop(cx, pat, arg, body, expr, span);
657+
self.check_for_loop(cx, pat, arg, body, expr, span);
649658
if let ExprKind::Block(block, _) = body.kind {
650659
never_loop::check(cx, block, loop_id, span, for_loop.as_ref());
651660
}
@@ -680,44 +689,47 @@ impl<'tcx> LateLintPass<'tcx> for Loops {
680689
}
681690
}
682691

683-
fn check_for_loop<'tcx>(
684-
cx: &LateContext<'tcx>,
685-
pat: &'tcx Pat<'_>,
686-
arg: &'tcx Expr<'_>,
687-
body: &'tcx Expr<'_>,
688-
expr: &'tcx Expr<'_>,
689-
span: Span,
690-
) {
691-
let is_manual_memcpy_triggered = manual_memcpy::check(cx, pat, arg, body, expr);
692-
if !is_manual_memcpy_triggered {
693-
needless_range_loop::check(cx, pat, arg, body, expr);
694-
explicit_counter_loop::check(cx, pat, arg, body, expr);
692+
impl Loops {
693+
fn check_for_loop<'tcx>(
694+
&self,
695+
cx: &LateContext<'tcx>,
696+
pat: &'tcx Pat<'_>,
697+
arg: &'tcx Expr<'_>,
698+
body: &'tcx Expr<'_>,
699+
expr: &'tcx Expr<'_>,
700+
span: Span,
701+
) {
702+
let is_manual_memcpy_triggered = manual_memcpy::check(cx, pat, arg, body, expr);
703+
if !is_manual_memcpy_triggered {
704+
needless_range_loop::check(cx, pat, arg, body, expr);
705+
explicit_counter_loop::check(cx, pat, arg, body, expr);
706+
}
707+
self.check_for_loop_arg(cx, pat, arg);
708+
for_kv_map::check(cx, pat, arg, body);
709+
mut_range_bound::check(cx, arg, body);
710+
single_element_loop::check(cx, pat, arg, body, expr);
711+
same_item_push::check(cx, pat, arg, body, expr);
712+
manual_flatten::check(cx, pat, arg, body, span);
713+
manual_find::check(cx, pat, arg, body, span, expr);
695714
}
696-
check_for_loop_arg(cx, pat, arg);
697-
for_kv_map::check(cx, pat, arg, body);
698-
mut_range_bound::check(cx, arg, body);
699-
single_element_loop::check(cx, pat, arg, body, expr);
700-
same_item_push::check(cx, pat, arg, body, expr);
701-
manual_flatten::check(cx, pat, arg, body, span);
702-
manual_find::check(cx, pat, arg, body, span, expr);
703-
}
704715

705-
fn check_for_loop_arg(cx: &LateContext<'_>, _: &Pat<'_>, arg: &Expr<'_>) {
706-
if let ExprKind::MethodCall(method, self_arg, [], _) = arg.kind {
707-
let method_name = method.ident.as_str();
708-
// check for looping over x.iter() or x.iter_mut(), could use &x or &mut x
709-
match method_name {
710-
"iter" | "iter_mut" => {
711-
explicit_iter_loop::check(cx, self_arg, arg, method_name);
712-
},
713-
"into_iter" => {
714-
explicit_iter_loop::check(cx, self_arg, arg, method_name);
715-
explicit_into_iter_loop::check(cx, self_arg, arg);
716-
},
717-
"next" => {
718-
iter_next_loop::check(cx, arg);
719-
},
720-
_ => {},
716+
fn check_for_loop_arg(&self, cx: &LateContext<'_>, _: &Pat<'_>, arg: &Expr<'_>) {
717+
if let ExprKind::MethodCall(method, self_arg, [], _) = arg.kind {
718+
let method_name = method.ident.as_str();
719+
// check for looping over x.iter() or x.iter_mut(), could use &x or &mut x
720+
match method_name {
721+
"iter" | "iter_mut" => {
722+
explicit_iter_loop::check(cx, self_arg, arg, method_name, &self.msrv);
723+
},
724+
"into_iter" => {
725+
explicit_iter_loop::check(cx, self_arg, arg, method_name, &self.msrv);
726+
explicit_into_iter_loop::check(cx, self_arg, arg);
727+
},
728+
"next" => {
729+
iter_next_loop::check(cx, arg);
730+
},
731+
_ => {},
732+
}
721733
}
722734
}
723735
}

clippy_utils/src/msrvs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ msrv_aliases! {
2828
1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST }
2929
1,51,0 { BORROW_AS_PTR, SEEK_FROM_CURRENT, UNSIGNED_ABS }
3030
1,50,0 { BOOL_THEN, CLAMP }
31-
1,47,0 { TAU, IS_ASCII_DIGIT_CONST }
31+
1,47,0 { TAU, IS_ASCII_DIGIT_CONST, ARRAY_IMPL_ANY_LEN }
3232
1,46,0 { CONST_IF_MATCH }
3333
1,45,0 { STR_STRIP_PREFIX }
3434
1,43,0 { LOG2_10, LOG10_2 }

0 commit comments

Comments
 (0)