1 use super::{get_span_of_entire_for_loop, IncrementVisitor, InitializeVisitor, MANUAL_MEMCPY}
;
2 use clippy_utils
::diagnostics
::span_lint_and_sugg
;
3 use clippy_utils
::source
::snippet
;
4 use clippy_utils
::sugg
::Sugg
;
5 use clippy_utils
::ty
::is_type_diagnostic_item
;
6 use clippy_utils
::{get_enclosing_block, higher, path_to_local, sugg}
;
7 use if_chain
::if_chain
;
9 use rustc_errors
::Applicability
;
10 use rustc_hir
::intravisit
::walk_block
;
11 use rustc_hir
::{BinOpKind, Block, Expr, ExprKind, HirId, Pat, PatKind, StmtKind}
;
12 use rustc_lint
::LateContext
;
13 use rustc_middle
::ty
::{self, Ty}
;
14 use rustc_span
::symbol
::sym
;
15 use std
::iter
::Iterator
;
17 /// Checks for for loops that sequentially copy items from one slice-like
18 /// object to another.
19 pub(super) fn check
<'tcx
>(
20 cx
: &LateContext
<'tcx
>,
26 if let Some(higher
::Range
{
30 }) = higher
::range(arg
)
32 // the var must be a single name
33 if let PatKind
::Binding(_
, canonical_id
, _
, _
) = pat
.kind
{
34 let mut starts
= vec
![Start
{
36 kind
: StartKind
::Range
,
39 // This is one of few ways to return different iterators
40 // derived from: https://stackoverflow.com/questions/29760668/conditionally-iterate-over-one-of-several-possible-iterators/52064434#52064434
41 let mut iter_a
= None
;
42 let mut iter_b
= None
;
44 if let ExprKind
::Block(block
, _
) = body
.kind
{
45 if let Some(loop_counters
) = get_loop_counters(cx
, block
, expr
) {
46 starts
.extend(loop_counters
);
48 iter_a
= Some(get_assignments(block
, &starts
));
50 iter_b
= Some(get_assignment(body
));
53 let assignments
= iter_a
.into_iter().flatten().chain(iter_b
.into_iter());
55 let big_sugg
= assignments
56 // The only statements in the for loops can be indexed assignments from
57 // indexed retrievals (except increments of loop counters).
59 o
.and_then(|(lhs
, rhs
)| {
60 let rhs
= fetch_cloned_expr(rhs
);
62 if let ExprKind
::Index(base_left
, idx_left
) = lhs
.kind
;
63 if let ExprKind
::Index(base_right
, idx_right
) = rhs
.kind
;
64 if is_slice_like(cx
, cx
.typeck_results().expr_ty(base_left
));
65 if is_slice_like(cx
, cx
.typeck_results().expr_ty(base_right
));
66 if let Some((start_left
, offset_left
)) = get_details_from_idx(cx
, idx_left
, &starts
);
67 if let Some((start_right
, offset_right
)) = get_details_from_idx(cx
, idx_right
, &starts
);
69 // Source and destination must be different
70 if path_to_local(base_left
) != path_to_local(base_right
);
72 Some((IndexExpr { base: base_left, idx: start_left, idx_offset: offset_left }
,
73 IndexExpr { base: base_right, idx: start_right, idx_offset: offset_right }
))
80 .map(|o
| o
.map(|(dst
, src
)| build_manual_memcpy_suggestion(cx
, start
, end
, limits
, &dst
, &src
)))
81 .collect
::<Option
<Vec
<_
>>>()
82 .filter(|v
| !v
.is_empty())
83 .map(|v
| v
.join("\n "));
85 if let Some(big_sugg
) = big_sugg
{
89 get_span_of_entire_for_loop(expr
),
90 "it looks like you're manually copying between slices",
91 "try replacing the loop by",
93 Applicability
::Unspecified
,
102 fn build_manual_memcpy_suggestion
<'tcx
>(
103 cx
: &LateContext
<'tcx
>,
106 limits
: ast
::RangeLimits
,
110 fn print_offset(offset
: MinifyingSugg
<'
static>) -> MinifyingSugg
<'
static> {
111 if offset
.as_str() == "0" {
118 let print_limit
= |end
: &Expr
<'_
>, end_str
: &str, base
: &Expr
<'_
>, sugg
: MinifyingSugg
<'
static>| {
120 if let ExprKind
::MethodCall(method
, _
, len_args
, _
) = end
.kind
;
121 if method
.ident
.name
== sym
::len
;
122 if len_args
.len() == 1;
123 if let Some(arg
) = len_args
.get(0);
124 if path_to_local(arg
) == path_to_local(base
);
126 if sugg
.as_str() == end_str
{
133 ast
::RangeLimits
::Closed
=> {
134 sugg
+ &sugg
::ONE
.into()
136 ast
::RangeLimits
::HalfOpen
=> sugg
,
142 let start_str
= Sugg
::hir(cx
, start
, "").into();
143 let end_str
: MinifyingSugg
<'_
> = Sugg
::hir(cx
, end
, "").into();
145 let print_offset_and_limit
= |idx_expr
: &IndexExpr
<'_
>| match idx_expr
.idx
{
146 StartKind
::Range
=> (
147 print_offset(apply_offset(&start_str
, &idx_expr
.idx_offset
)).into_sugg(),
152 apply_offset(&end_str
, &idx_expr
.idx_offset
),
156 StartKind
::Counter { initializer }
=> {
157 let counter_start
= Sugg
::hir(cx
, initializer
, "").into();
159 print_offset(apply_offset(&counter_start
, &idx_expr
.idx_offset
)).into_sugg(),
164 apply_offset(&end_str
, &idx_expr
.idx_offset
) + &counter_start
- &start_str
,
171 let (dst_offset
, dst_limit
) = print_offset_and_limit(dst
);
172 let (src_offset
, src_limit
) = print_offset_and_limit(src
);
174 let dst_base_str
= snippet(cx
, dst
.base
.span
, "???");
175 let src_base_str
= snippet(cx
, src
.base
.span
, "???");
177 let dst
= if dst_offset
== sugg
::EMPTY
&& dst_limit
== sugg
::EMPTY
{
183 dst_offset
.maybe_par(),
184 dst_limit
.maybe_par()
190 "{}.clone_from_slice(&{}[{}..{}]);",
193 src_offset
.maybe_par(),
194 src_limit
.maybe_par()
198 /// a wrapper of `Sugg`. Besides what `Sugg` do, this removes unnecessary `0`;
199 /// and also, it avoids subtracting a variable from the same one by replacing it with `0`.
200 /// it exists for the convenience of the overloaded operators while normal functions can do the
203 struct MinifyingSugg
<'a
>(Sugg
<'a
>);
205 impl<'a
> MinifyingSugg
<'a
> {
206 fn as_str(&self) -> &str {
207 // HACK: Don't sync to Clippy! Required because something with the `or_patterns` feature
208 // changed and this would now require parentheses.
210 Sugg
::NonParen(s
) | Sugg
::MaybeParen(s
) | Sugg
::BinOp(_
, s
) => s
.as_ref(),
214 fn into_sugg(self) -> Sugg
<'a
> {
219 impl<'a
> From
<Sugg
<'a
>> for MinifyingSugg
<'a
> {
220 fn from(sugg
: Sugg
<'a
>) -> Self {
225 impl std
::ops
::Add
for &MinifyingSugg
<'
static> {
226 type Output
= MinifyingSugg
<'
static>;
227 fn add(self, rhs
: &MinifyingSugg
<'
static>) -> MinifyingSugg
<'
static> {
228 match (self.as_str(), rhs
.as_str()) {
229 ("0", _
) => rhs
.clone(),
230 (_
, "0") => self.clone(),
231 (_
, _
) => (&self.0 + &rhs
.0).into(),
236 impl std
::ops
::Sub
for &MinifyingSugg
<'
static> {
237 type Output
= MinifyingSugg
<'
static>;
238 fn sub(self, rhs
: &MinifyingSugg
<'
static>) -> MinifyingSugg
<'
static> {
239 match (self.as_str(), rhs
.as_str()) {
240 (_
, "0") => self.clone(),
241 ("0", _
) => (-rhs
.0.clone()).into(),
242 (x
, y
) if x
== y
=> sugg
::ZERO
.into(),
243 (_
, _
) => (&self.0 - &rhs
.0).into(),
248 impl std
::ops
::Add
<&MinifyingSugg
<'
static>> for MinifyingSugg
<'
static> {
249 type Output
= MinifyingSugg
<'
static>;
250 fn add(self, rhs
: &MinifyingSugg
<'
static>) -> MinifyingSugg
<'
static> {
251 match (self.as_str(), rhs
.as_str()) {
252 ("0", _
) => rhs
.clone(),
254 (_
, _
) => (self.0 + &rhs
.0).into(),
259 impl std
::ops
::Sub
<&MinifyingSugg
<'
static>> for MinifyingSugg
<'
static> {
260 type Output
= MinifyingSugg
<'
static>;
261 fn sub(self, rhs
: &MinifyingSugg
<'
static>) -> MinifyingSugg
<'
static> {
262 match (self.as_str(), rhs
.as_str()) {
264 ("0", _
) => (-rhs
.0.clone()).into(),
265 (x
, y
) if x
== y
=> sugg
::ZERO
.into(),
266 (_
, _
) => (self.0 - &rhs
.0).into(),
271 /// a wrapper around `MinifyingSugg`, which carries a operator like currying
272 /// so that the suggested code become more efficient (e.g. `foo + -bar` `foo - bar`).
274 value
: MinifyingSugg
<'
static>,
278 #[derive(Clone, Copy)]
285 fn negative(value
: Sugg
<'
static>) -> Self {
288 sign
: OffsetSign
::Negative
,
292 fn positive(value
: Sugg
<'
static>) -> Self {
295 sign
: OffsetSign
::Positive
,
300 Self::positive(sugg
::ZERO
)
304 fn apply_offset(lhs
: &MinifyingSugg
<'
static>, rhs
: &Offset
) -> MinifyingSugg
<'
static> {
306 OffsetSign
::Positive
=> lhs
+ &rhs
.value
,
307 OffsetSign
::Negative
=> lhs
- &rhs
.value
,
311 #[derive(Debug, Clone, Copy)]
312 enum StartKind
<'hir
> {
314 Counter { initializer: &'hir Expr<'hir> }
,
317 struct IndexExpr
<'hir
> {
318 base
: &'hir Expr
<'hir
>,
319 idx
: StartKind
<'hir
>,
325 kind
: StartKind
<'hir
>,
328 fn is_slice_like
<'tcx
>(cx
: &LateContext
<'tcx
>, ty
: Ty
<'_
>) -> bool
{
329 let is_slice
= match ty
.kind() {
330 ty
::Ref(_
, subty
, _
) => is_slice_like(cx
, subty
),
331 ty
::Slice(..) | ty
::Array(..) => true,
335 is_slice
|| is_type_diagnostic_item(cx
, ty
, sym
::vec_type
) || is_type_diagnostic_item(cx
, ty
, sym
::vecdeque_type
)
338 fn fetch_cloned_expr
<'tcx
>(expr
: &'tcx Expr
<'tcx
>) -> &'tcx Expr
<'tcx
> {
340 if let ExprKind
::MethodCall(method
, _
, args
, _
) = expr
.kind
;
341 if method
.ident
.name
== sym
::clone
;
343 if let Some(arg
) = args
.get(0);
344 then { arg }
else { expr }
348 fn get_details_from_idx
<'tcx
>(
349 cx
: &LateContext
<'tcx
>,
351 starts
: &[Start
<'tcx
>],
352 ) -> Option
<(StartKind
<'tcx
>, Offset
)> {
353 fn get_start
<'tcx
>(e
: &Expr
<'_
>, starts
: &[Start
<'tcx
>]) -> Option
<StartKind
<'tcx
>> {
354 let id
= path_to_local(e
)?
;
355 starts
.iter().find(|start
| start
.id
== id
).map(|start
| start
.kind
)
358 fn get_offset
<'tcx
>(cx
: &LateContext
<'tcx
>, e
: &Expr
<'_
>, starts
: &[Start
<'tcx
>]) -> Option
<Sugg
<'
static>> {
360 ExprKind
::Lit(l
) => match l
.node
{
361 ast
::LitKind
::Int(x
, _ty
) => Some(Sugg
::NonParen(x
.to_string().into())),
364 ExprKind
::Path(..) if get_start(e
, starts
).is_none() => Some(Sugg
::hir(cx
, e
, "???")),
370 ExprKind
::Binary(op
, lhs
, rhs
) => match op
.node
{
372 let offset_opt
= get_start(lhs
, starts
)
373 .and_then(|s
| get_offset(cx
, rhs
, starts
).map(|o
| (s
, o
)))
374 .or_else(|| get_start(rhs
, starts
).and_then(|s
| get_offset(cx
, lhs
, starts
).map(|o
| (s
, o
))));
376 offset_opt
.map(|(s
, o
)| (s
, Offset
::positive(o
)))
379 get_start(lhs
, starts
).and_then(|s
| get_offset(cx
, rhs
, starts
).map(|o
| (s
, Offset
::negative(o
))))
383 ExprKind
::Path(..) => get_start(idx
, starts
).map(|s
| (s
, Offset
::empty())),
388 fn get_assignment
<'tcx
>(e
: &'tcx Expr
<'tcx
>) -> Option
<(&'tcx Expr
<'tcx
>, &'tcx Expr
<'tcx
>)> {
389 if let ExprKind
::Assign(lhs
, rhs
, _
) = e
.kind
{
396 /// Get assignments from the given block.
397 /// The returned iterator yields `None` if no assignment expressions are there,
398 /// filtering out the increments of the given whitelisted loop counters;
399 /// because its job is to make sure there's nothing other than assignments and the increments.
400 fn get_assignments
<'a
, 'tcx
>(
401 Block { stmts, expr, .. }
: &'tcx Block
<'tcx
>,
402 loop_counters
: &'a
[Start
<'tcx
>],
403 ) -> impl Iterator
<Item
= Option
<(&'tcx Expr
<'tcx
>, &'tcx Expr
<'tcx
>)>> + 'a
{
404 // As the `filter` and `map` below do different things, I think putting together
405 // just increases complexity. (cc #3188 and #4193)
408 .filter_map(move |stmt
| match stmt
.kind
{
409 StmtKind
::Local(..) | StmtKind
::Item(..) => None
,
410 StmtKind
::Expr(e
) | StmtKind
::Semi(e
) => Some(e
),
412 .chain((*expr
).into_iter())
414 if let ExprKind
::AssignOp(_
, place
, _
) = e
.kind
{
415 path_to_local(place
).map_or(false, |id
| {
418 // skip the first item which should be `StartKind::Range`
419 // this makes it possible to use the slice with `StartKind::Range` in the same iterator loop.
421 .any(|counter
| counter
.id
== id
)
430 fn get_loop_counters
<'a
, 'tcx
>(
431 cx
: &'a LateContext
<'tcx
>,
432 body
: &'tcx Block
<'tcx
>,
433 expr
: &'tcx Expr
<'_
>,
434 ) -> Option
<impl Iterator
<Item
= Start
<'tcx
>> + 'a
> {
435 // Look for variables that are incremented once per loop iteration.
436 let mut increment_visitor
= IncrementVisitor
::new(cx
);
437 walk_block(&mut increment_visitor
, body
);
439 // For each candidate, check the parent block to see if
440 // it's initialized to zero at the start of the loop.
441 get_enclosing_block(cx
, expr
.hir_id
).and_then(|block
| {
444 .filter_map(move |var_id
| {
445 let mut initialize_visitor
= InitializeVisitor
::new(cx
, expr
, var_id
);
446 walk_block(&mut initialize_visitor
, block
);
448 initialize_visitor
.get_result().map(|(_
, initializer
)| Start
{
450 kind
: StartKind
::Counter { initializer }
,