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