]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/methods/excessive_for_each.rs
New Lint: excessive_for_each
[rust.git] / clippy_lints / src / methods / excessive_for_each.rs
1 use rustc_errors::Applicability;
2 use rustc_hir::{
3     intravisit::{walk_expr, NestedVisitorMap, Visitor},
4     Expr, ExprKind,
5 };
6 use rustc_lint::LateContext;
7 use rustc_middle::{hir::map::Map, ty, ty::Ty};
8 use rustc_span::source_map::Span;
9
10 use crate::utils::{match_trait_method, match_type, paths, snippet, span_lint_and_then};
11
12 use if_chain::if_chain;
13
14 pub(super) fn lint(cx: &LateContext<'_>, expr: &'tcx Expr<'_>, args: &[&[Expr<'_>]]) {
15     if args.len() < 2 {
16         return;
17     }
18
19     let for_each_args = args[0];
20     if for_each_args.len() < 2 {
21         return;
22     }
23     let for_each_receiver = &for_each_args[0];
24     let for_each_arg = &for_each_args[1];
25     let iter_receiver = &args[1][0];
26
27     if_chain! {
28         if match_trait_method(cx, expr, &paths::ITERATOR);
29         if !match_trait_method(cx, for_each_receiver, &paths::ITERATOR);
30         if is_target_ty(cx, cx.typeck_results().expr_ty(iter_receiver));
31         if let ExprKind::Closure(_, _, body_id, ..) = for_each_arg.kind;
32         then {
33             let body = cx.tcx.hir().body(body_id);
34             let mut ret_span_collector = RetSpanCollector::new();
35             ret_span_collector.visit_expr(&body.value);
36
37             let label = "'outer";
38             let loop_label = if ret_span_collector.need_label {
39                 format!("{}: ", label)
40             } else {
41                 "".to_string()
42             };
43             let sugg =
44                 format!("{}for {} in {} {{ .. }}", loop_label, snippet(cx, body.params[0].pat.span, ""), snippet(cx, for_each_receiver.span, ""));
45
46             let mut notes = vec![];
47             for (span, need_label) in ret_span_collector.spans {
48                 let cont_label = if need_label {
49                     format!(" {}", label)
50                 } else {
51                     "".to_string()
52                 };
53                 let note = format!("change `return` to `continue{}` in the loop body", cont_label);
54                 notes.push((span, note));
55             }
56
57             span_lint_and_then(cx,
58                       super::EXCESSIVE_FOR_EACH,
59                       expr.span,
60                       "excessive use of `for_each`",
61                       |diag| {
62                           diag.span_suggestion(expr.span, "try", sugg, Applicability::HasPlaceholders);
63                           for note in notes {
64                               diag.span_note(note.0, &note.1);
65                           }
66                       }
67                 );
68         }
69     }
70 }
71
72 type PathSegment = &'static [&'static str];
73
74 const TARGET_ITER_RECEIVER_TY: &[PathSegment] = &[
75     &paths::VEC,
76     &paths::VEC_DEQUE,
77     &paths::LINKED_LIST,
78     &paths::HASHMAP,
79     &paths::BTREEMAP,
80     &paths::HASHSET,
81     &paths::BTREESET,
82     &paths::BINARY_HEAP,
83 ];
84
85 fn is_target_ty(cx: &LateContext<'_>, expr_ty: Ty<'_>) -> bool {
86     let expr_ty = expr_ty.peel_refs();
87     for target in TARGET_ITER_RECEIVER_TY {
88         if match_type(cx, expr_ty, target) {
89             return true;
90         }
91     }
92
93     if_chain! {
94         if matches!(expr_ty.kind(), ty::Slice(_) | ty::Array(..));
95         then {
96             return true;
97         }
98     }
99
100     false
101 }
102
103 /// Collect spans of `return` in the closure body.
104 struct RetSpanCollector {
105     spans: Vec<(Span, bool)>,
106     loop_depth: u16,
107     need_label: bool,
108 }
109
110 impl RetSpanCollector {
111     fn new() -> Self {
112         Self {
113             spans: Vec::new(),
114             loop_depth: 0,
115             need_label: false,
116         }
117     }
118 }
119
120 impl<'tcx> Visitor<'tcx> for RetSpanCollector {
121     type Map = Map<'tcx>;
122
123     fn visit_expr(&mut self, expr: &Expr<'_>) {
124         match expr.kind {
125             ExprKind::Ret(..) => {
126                 if self.loop_depth > 0 && !self.need_label {
127                     self.need_label = true
128                 }
129
130                 self.spans.push((expr.span, self.loop_depth > 0))
131             },
132
133             ExprKind::Loop(..) => {
134                 self.loop_depth += 1;
135                 walk_expr(self, expr);
136                 self.loop_depth -= 1;
137                 return;
138             },
139
140             _ => {},
141         }
142
143         walk_expr(self, expr);
144     }
145
146     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
147         NestedVisitorMap::None
148     }
149 }