1
1
use super::EXPLICIT_ITER_LOOP;
2
2
use clippy_utils::diagnostics::span_lint_and_sugg;
3
3
use clippy_utils::is_trait_method;
4
+ use clippy_utils::msrvs::{self, Msrv};
4
5
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};
6
8
use rustc_errors::Applicability;
7
9
use rustc_hir::{Expr, Mutability};
8
10
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};
10
13
use rustc_span::sym;
11
14
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) => {
16
35
let receiver_ty = cx.typeck_results().expr_ty(self_arg);
17
36
let receiver_ty_adjusted = cx.typeck_results().expr_ty_adjusted(self_arg);
18
37
let ref_receiver_ty = cx.tcx.mk_ref(
@@ -22,54 +41,159 @@ pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, arg: &Expr<'_>, m
22
41
mutbl: Mutability::Not,
23
42
},
24
43
);
25
- receiver_ty_adjusted == ref_receiver_ty
44
+ if receiver_ty_adjusted == ref_receiver_ty {
45
+ AdjustKind::None
46
+ } else {
47
+ return;
48
+ }
26
49
},
27
- _ => false ,
50
+ _ => return ,
28
51
};
29
52
30
- if !should_lint {
31
- return;
32
- }
33
-
34
53
let mut applicability = Applicability::MachineApplicable;
35
54
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
+ };
37
63
span_lint_and_sugg(
38
64
cx,
39
65
EXPLICIT_ITER_LOOP,
40
- arg .span,
66
+ call_expr .span,
41
67
"it is more concise to loop over references to containers instead of using explicit \
42
68
iteration methods",
43
69
"to write this more concisely, try",
44
- format!("&{muta }{object}"),
70
+ format!("{prefix }{object}"),
45
71
applicability,
46
72
);
47
73
}
48
74
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
+ }
65
104
}
66
105
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
74
198
}
75
199
}
0 commit comments