1 use super::{get_span_of_entire_for_loop, IncrementVisitor, InitializeVisitor, MANUAL_MEMCPY}
;
2 use crate::utils
::sugg
::Sugg
;
4 get_enclosing_block
, higher
, is_type_diagnostic_item
, path_to_local
, snippet
, span_lint_and_sugg
, sugg
,
6 use if_chain
::if_chain
;
8 use rustc_errors
::Applicability
;
9 use rustc_hir
::intravisit
::walk_block
;
10 use rustc_hir
::{BinOpKind, Block, Expr, ExprKind, HirId, Pat, PatKind, StmtKind}
;
11 use rustc_lint
::LateContext
;
12 use rustc_middle
::ty
::{self, Ty}
;
13 use rustc_span
::symbol
::sym
;
14 use std
::iter
::Iterator
;
16 /// Checks for for loops that sequentially copy items from one slice-like
17 /// object to another.
18 pub(super) fn check
<'tcx
>(
19 cx
: &LateContext
<'tcx
>,
25 if let Some(higher
::Range
{
29 }) = higher
::range(arg
)
31 // the var must be a single name
32 if let PatKind
::Binding(_
, canonical_id
, _
, _
) = pat
.kind
{
33 let mut starts
= vec
![Start
{
35 kind
: StartKind
::Range
,
38 // This is one of few ways to return different iterators
39 // derived from: https://stackoverflow.com/questions/29760668/conditionally-iterate-over-one-of-several-possible-iterators/52064434#52064434
40 let mut iter_a
= None
;
41 let mut iter_b
= None
;
43 if let ExprKind
::Block(block
, _
) = body
.kind
{
44 if let Some(loop_counters
) = get_loop_counters(cx
, block
, expr
) {
45 starts
.extend(loop_counters
);
47 iter_a
= Some(get_assignments(block
, &starts
));
49 iter_b
= Some(get_assignment(body
));
52 let assignments
= iter_a
.into_iter().flatten().chain(iter_b
.into_iter());
54 let big_sugg
= assignments
55 // The only statements in the for loops can be indexed assignments from
56 // indexed retrievals (except increments of loop counters).
58 o
.and_then(|(lhs
, rhs
)| {
59 let rhs
= fetch_cloned_expr(rhs
);
61 if let ExprKind
::Index(base_left
, idx_left
) = lhs
.kind
;
62 if let ExprKind
::Index(base_right
, idx_right
) = rhs
.kind
;
63 if is_slice_like(cx
, cx
.typeck_results().expr_ty(base_left
))
64 && is_slice_like(cx
, cx
.typeck_results().expr_ty(base_right
));
65 if let Some((start_left
, offset_left
)) = get_details_from_idx(cx
, &idx_left
, &starts
);
66 if let Some((start_right
, offset_right
)) = get_details_from_idx(cx
, &idx_right
, &starts
);
68 // Source and destination must be different
69 if path_to_local(base_left
) != path_to_local(base_right
);
71 Some((IndexExpr { base: base_left, idx: start_left, idx_offset: offset_left }
,
72 IndexExpr { base: base_right, idx: start_right, idx_offset: offset_right }
))
79 .map(|o
| o
.map(|(dst
, src
)| build_manual_memcpy_suggestion(cx
, start
, end
, limits
, &dst
, &src
)))
80 .collect
::<Option
<Vec
<_
>>>()
81 .filter(|v
| !v
.is_empty())
82 .map(|v
| v
.join("\n "));
84 if let Some(big_sugg
) = big_sugg
{
88 get_span_of_entire_for_loop(expr
),
89 "it looks like you're manually copying between slices",
90 "try replacing the loop by",
92 Applicability
::Unspecified
,
101 fn build_manual_memcpy_suggestion
<'tcx
>(
102 cx
: &LateContext
<'tcx
>,
105 limits
: ast
::RangeLimits
,
109 fn print_offset(offset
: MinifyingSugg
<'
static>) -> MinifyingSugg
<'
static> {
110 if offset
.as_str() == "0" {
117 let print_limit
= |end
: &Expr
<'_
>, end_str
: &str, base
: &Expr
<'_
>, sugg
: MinifyingSugg
<'
static>| {
119 if let ExprKind
::MethodCall(method
, _
, len_args
, _
) = end
.kind
;
120 if method
.ident
.name
== sym
!(len
);
121 if len_args
.len() == 1;
122 if let Some(arg
) = len_args
.get(0);
123 if path_to_local(arg
) == path_to_local(base
);
125 if sugg
.as_str() == end_str
{
132 ast
::RangeLimits
::Closed
=> {
133 sugg
+ &sugg
::ONE
.into()
135 ast
::RangeLimits
::HalfOpen
=> sugg
,
141 let start_str
= Sugg
::hir(cx
, start
, "").into();
142 let end_str
: MinifyingSugg
<'_
> = Sugg
::hir(cx
, end
, "").into();
144 let print_offset_and_limit
= |idx_expr
: &IndexExpr
<'_
>| match idx_expr
.idx
{
145 StartKind
::Range
=> (
146 print_offset(apply_offset(&start_str
, &idx_expr
.idx_offset
)).into_sugg(),
151 apply_offset(&end_str
, &idx_expr
.idx_offset
),
155 StartKind
::Counter { initializer }
=> {
156 let counter_start
= Sugg
::hir(cx
, initializer
, "").into();
158 print_offset(apply_offset(&counter_start
, &idx_expr
.idx_offset
)).into_sugg(),
163 apply_offset(&end_str
, &idx_expr
.idx_offset
) + &counter_start
- &start_str
,
170 let (dst_offset
, dst_limit
) = print_offset_and_limit(&dst
);
171 let (src_offset
, src_limit
) = print_offset_and_limit(&src
);
173 let dst_base_str
= snippet(cx
, dst
.base
.span
, "???");
174 let src_base_str
= snippet(cx
, src
.base
.span
, "???");
176 let dst
= if dst_offset
== sugg
::EMPTY
&& dst_limit
== sugg
::EMPTY
{
182 dst_offset
.maybe_par(),
183 dst_limit
.maybe_par()
189 "{}.clone_from_slice(&{}[{}..{}]);",
192 src_offset
.maybe_par(),
193 src_limit
.maybe_par()
197 /// a wrapper of `Sugg`. Besides what `Sugg` do, this removes unnecessary `0`;
198 /// and also, it avoids subtracting a variable from the same one by replacing it with `0`.
199 /// it exists for the convenience of the overloaded operators while normal functions can do the
202 struct MinifyingSugg
<'a
>(Sugg
<'a
>);
204 impl<'a
> MinifyingSugg
<'a
> {
205 fn as_str(&self) -> &str {
206 // HACK: Don't sync to Clippy! Required because something with the `or_patterns` feature
207 // changed and this would now require parentheses.
209 Sugg
::NonParen(s
) | Sugg
::MaybeParen(s
) | Sugg
::BinOp(_
, s
) => s
.as_ref(),
213 fn into_sugg(self) -> Sugg
<'a
> {
218 impl<'a
> From
<Sugg
<'a
>> for MinifyingSugg
<'a
> {
219 fn from(sugg
: Sugg
<'a
>) -> Self {
224 impl std
::ops
::Add
for &MinifyingSugg
<'
static> {
225 type Output
= MinifyingSugg
<'
static>;
226 fn add(self, rhs
: &MinifyingSugg
<'
static>) -> MinifyingSugg
<'
static> {
227 match (self.as_str(), rhs
.as_str()) {
228 ("0", _
) => rhs
.clone(),
229 (_
, "0") => self.clone(),
230 (_
, _
) => (&self.0 + &rhs
.0).into(),
235 impl std
::ops
::Sub
for &MinifyingSugg
<'
static> {
236 type Output
= MinifyingSugg
<'
static>;
237 fn sub(self, rhs
: &MinifyingSugg
<'
static>) -> MinifyingSugg
<'
static> {
238 match (self.as_str(), rhs
.as_str()) {
239 (_
, "0") => self.clone(),
240 ("0", _
) => (-rhs
.0.clone()).into(),
241 (x
, y
) if x
== y
=> sugg
::ZERO
.into(),
242 (_
, _
) => (&self.0 - &rhs
.0).into(),
247 impl std
::ops
::Add
<&MinifyingSugg
<'
static>> for MinifyingSugg
<'
static> {
248 type Output
= MinifyingSugg
<'
static>;
249 fn add(self, rhs
: &MinifyingSugg
<'
static>) -> MinifyingSugg
<'
static> {
250 match (self.as_str(), rhs
.as_str()) {
251 ("0", _
) => rhs
.clone(),
253 (_
, _
) => (self.0 + &rhs
.0).into(),
258 impl std
::ops
::Sub
<&MinifyingSugg
<'
static>> for MinifyingSugg
<'
static> {
259 type Output
= MinifyingSugg
<'
static>;
260 fn sub(self, rhs
: &MinifyingSugg
<'
static>) -> MinifyingSugg
<'
static> {
261 match (self.as_str(), rhs
.as_str()) {
263 ("0", _
) => (-rhs
.0.clone()).into(),
264 (x
, y
) if x
== y
=> sugg
::ZERO
.into(),
265 (_
, _
) => (self.0 - &rhs
.0).into(),
270 /// a wrapper around `MinifyingSugg`, which carries a operator like currying
271 /// so that the suggested code become more efficient (e.g. `foo + -bar` `foo - bar`).
273 value
: MinifyingSugg
<'
static>,
277 #[derive(Clone, Copy)]
284 fn negative(value
: Sugg
<'
static>) -> Self {
287 sign
: OffsetSign
::Negative
,
291 fn positive(value
: Sugg
<'
static>) -> Self {
294 sign
: OffsetSign
::Positive
,
299 Self::positive(sugg
::ZERO
)
303 fn apply_offset(lhs
: &MinifyingSugg
<'
static>, rhs
: &Offset
) -> MinifyingSugg
<'
static> {
305 OffsetSign
::Positive
=> lhs
+ &rhs
.value
,
306 OffsetSign
::Negative
=> lhs
- &rhs
.value
,
310 #[derive(Debug, Clone, Copy)]
311 enum StartKind
<'hir
> {
313 Counter { initializer: &'hir Expr<'hir> }
,
316 struct IndexExpr
<'hir
> {
317 base
: &'hir Expr
<'hir
>,
318 idx
: StartKind
<'hir
>,
324 kind
: StartKind
<'hir
>,
327 fn is_slice_like
<'tcx
>(cx
: &LateContext
<'tcx
>, ty
: Ty
<'_
>) -> bool
{
328 let is_slice
= match ty
.kind() {
329 ty
::Ref(_
, subty
, _
) => is_slice_like(cx
, subty
),
330 ty
::Slice(..) | ty
::Array(..) => true,
334 is_slice
|| is_type_diagnostic_item(cx
, ty
, sym
::vec_type
) || is_type_diagnostic_item(cx
, ty
, sym
::vecdeque_type
)
337 fn fetch_cloned_expr
<'tcx
>(expr
: &'tcx Expr
<'tcx
>) -> &'tcx Expr
<'tcx
> {
339 if let ExprKind
::MethodCall(method
, _
, args
, _
) = expr
.kind
;
340 if method
.ident
.name
== sym
::clone
;
342 if let Some(arg
) = args
.get(0);
343 then { arg }
else { expr }
347 fn get_details_from_idx
<'tcx
>(
348 cx
: &LateContext
<'tcx
>,
350 starts
: &[Start
<'tcx
>],
351 ) -> Option
<(StartKind
<'tcx
>, Offset
)> {
352 fn get_start
<'tcx
>(e
: &Expr
<'_
>, starts
: &[Start
<'tcx
>]) -> Option
<StartKind
<'tcx
>> {
353 let id
= path_to_local(e
)?
;
354 starts
.iter().find(|start
| start
.id
== id
).map(|start
| start
.kind
)
357 fn get_offset
<'tcx
>(cx
: &LateContext
<'tcx
>, e
: &Expr
<'_
>, starts
: &[Start
<'tcx
>]) -> Option
<Sugg
<'
static>> {
359 ExprKind
::Lit(l
) => match l
.node
{
360 ast
::LitKind
::Int(x
, _ty
) => Some(Sugg
::NonParen(x
.to_string().into())),
363 ExprKind
::Path(..) if get_start(e
, starts
).is_none() => Some(Sugg
::hir(cx
, e
, "???")),
369 ExprKind
::Binary(op
, lhs
, rhs
) => match op
.node
{
371 let offset_opt
= get_start(lhs
, starts
)
372 .and_then(|s
| get_offset(cx
, rhs
, starts
).map(|o
| (s
, o
)))
373 .or_else(|| get_start(rhs
, starts
).and_then(|s
| get_offset(cx
, lhs
, starts
).map(|o
| (s
, o
))));
375 offset_opt
.map(|(s
, o
)| (s
, Offset
::positive(o
)))
378 get_start(lhs
, starts
).and_then(|s
| get_offset(cx
, rhs
, starts
).map(|o
| (s
, Offset
::negative(o
))))
382 ExprKind
::Path(..) => get_start(idx
, starts
).map(|s
| (s
, Offset
::empty())),
387 fn get_assignment
<'tcx
>(e
: &'tcx Expr
<'tcx
>) -> Option
<(&'tcx Expr
<'tcx
>, &'tcx Expr
<'tcx
>)> {
388 if let ExprKind
::Assign(lhs
, rhs
, _
) = e
.kind
{
395 /// Get assignments from the given block.
396 /// The returned iterator yields `None` if no assignment expressions are there,
397 /// filtering out the increments of the given whitelisted loop counters;
398 /// because its job is to make sure there's nothing other than assignments and the increments.
399 fn get_assignments
<'a
, 'tcx
>(
400 Block { stmts, expr, .. }
: &'tcx Block
<'tcx
>,
401 loop_counters
: &'a
[Start
<'tcx
>],
402 ) -> impl Iterator
<Item
= Option
<(&'tcx Expr
<'tcx
>, &'tcx Expr
<'tcx
>)>> + 'a
{
403 // As the `filter` and `map` below do different things, I think putting together
404 // just increases complexity. (cc #3188 and #4193)
407 .filter_map(move |stmt
| match stmt
.kind
{
408 StmtKind
::Local(..) | StmtKind
::Item(..) => None
,
409 StmtKind
::Expr(e
) | StmtKind
::Semi(e
) => Some(e
),
411 .chain((*expr
).into_iter())
413 if let ExprKind
::AssignOp(_
, place
, _
) = e
.kind
{
414 path_to_local(place
).map_or(false, |id
| {
417 // skip the first item which should be `StartKind::Range`
418 // this makes it possible to use the slice with `StartKind::Range` in the same iterator loop.
420 .any(|counter
| counter
.id
== id
)
429 fn get_loop_counters
<'a
, 'tcx
>(
430 cx
: &'a LateContext
<'tcx
>,
431 body
: &'tcx Block
<'tcx
>,
432 expr
: &'tcx Expr
<'_
>,
433 ) -> Option
<impl Iterator
<Item
= Start
<'tcx
>> + 'a
> {
434 // Look for variables that are incremented once per loop iteration.
435 let mut increment_visitor
= IncrementVisitor
::new(cx
);
436 walk_block(&mut increment_visitor
, body
);
438 // For each candidate, check the parent block to see if
439 // it's initialized to zero at the start of the loop.
440 get_enclosing_block(&cx
, expr
.hir_id
).and_then(|block
| {
443 .filter_map(move |var_id
| {
444 let mut initialize_visitor
= InitializeVisitor
::new(cx
, expr
, var_id
);
445 walk_block(&mut initialize_visitor
, block
);
447 initialize_visitor
.get_result().map(|(_
, initializer
)| Start
{
449 kind
: StartKind
::Counter { initializer }
,