]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/loops/while_immutable_condition.rs
Rollup merge of #83092 - petrochenkov:qspan, r=estebank
[rust.git] / clippy_lints / src / loops / while_immutable_condition.rs
1 use super::WHILE_IMMUTABLE_CONDITION;
2 use crate::consts::constant;
3 use crate::utils::span_lint_and_then;
4 use crate::utils::usage::mutated_variables;
5 use if_chain::if_chain;
6 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
7 use rustc_hir::def::{DefKind, Res};
8 use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
9 use rustc_hir::{def_id, Expr, ExprKind, HirId, QPath};
10 use rustc_lint::LateContext;
11 use rustc_middle::hir::map::Map;
12 use std::iter::Iterator;
13
14 pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) {
15     if constant(cx, cx.typeck_results(), cond).is_some() {
16         // A pure constant condition (e.g., `while false`) is not linted.
17         return;
18     }
19
20     let mut var_visitor = VarCollectorVisitor {
21         cx,
22         ids: FxHashSet::default(),
23         def_ids: FxHashMap::default(),
24         skip: false,
25     };
26     var_visitor.visit_expr(cond);
27     if var_visitor.skip {
28         return;
29     }
30     let used_in_condition = &var_visitor.ids;
31     let no_cond_variable_mutated = if let Some(used_mutably) = mutated_variables(expr, cx) {
32         used_in_condition.is_disjoint(&used_mutably)
33     } else {
34         return;
35     };
36     let mutable_static_in_cond = var_visitor.def_ids.iter().any(|(_, v)| *v);
37
38     let mut has_break_or_return_visitor = HasBreakOrReturnVisitor {
39         has_break_or_return: false,
40     };
41     has_break_or_return_visitor.visit_expr(expr);
42     let has_break_or_return = has_break_or_return_visitor.has_break_or_return;
43
44     if no_cond_variable_mutated && !mutable_static_in_cond {
45         span_lint_and_then(
46             cx,
47             WHILE_IMMUTABLE_CONDITION,
48             cond.span,
49             "variables in the condition are not mutated in the loop body",
50             |diag| {
51                 diag.note("this may lead to an infinite or to a never running loop");
52
53                 if has_break_or_return {
54                     diag.note("this loop contains `return`s or `break`s");
55                     diag.help("rewrite it as `if cond { loop { } }`");
56                 }
57             },
58         );
59     }
60 }
61
62 struct HasBreakOrReturnVisitor {
63     has_break_or_return: bool,
64 }
65
66 impl<'tcx> Visitor<'tcx> for HasBreakOrReturnVisitor {
67     type Map = Map<'tcx>;
68
69     fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
70         if self.has_break_or_return {
71             return;
72         }
73
74         match expr.kind {
75             ExprKind::Ret(_) | ExprKind::Break(_, _) => {
76                 self.has_break_or_return = true;
77                 return;
78             },
79             _ => {},
80         }
81
82         walk_expr(self, expr);
83     }
84
85     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
86         NestedVisitorMap::None
87     }
88 }
89
90 /// Collects the set of variables in an expression
91 /// Stops analysis if a function call is found
92 /// Note: In some cases such as `self`, there are no mutable annotation,
93 /// All variables definition IDs are collected
94 struct VarCollectorVisitor<'a, 'tcx> {
95     cx: &'a LateContext<'tcx>,
96     ids: FxHashSet<HirId>,
97     def_ids: FxHashMap<def_id::DefId, bool>,
98     skip: bool,
99 }
100
101 impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> {
102     fn insert_def_id(&mut self, ex: &'tcx Expr<'_>) {
103         if_chain! {
104             if let ExprKind::Path(ref qpath) = ex.kind;
105             if let QPath::Resolved(None, _) = *qpath;
106             let res = self.cx.qpath_res(qpath, ex.hir_id);
107             then {
108                 match res {
109                     Res::Local(hir_id) => {
110                         self.ids.insert(hir_id);
111                     },
112                     Res::Def(DefKind::Static, def_id) => {
113                         let mutable = self.cx.tcx.is_mutable_static(def_id);
114                         self.def_ids.insert(def_id, mutable);
115                     },
116                     _ => {},
117                 }
118             }
119         }
120     }
121 }
122
123 impl<'a, 'tcx> Visitor<'tcx> for VarCollectorVisitor<'a, 'tcx> {
124     type Map = Map<'tcx>;
125
126     fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
127         match ex.kind {
128             ExprKind::Path(_) => self.insert_def_id(ex),
129             // If there is any function/method call… we just stop analysis
130             ExprKind::Call(..) | ExprKind::MethodCall(..) => self.skip = true,
131
132             _ => walk_expr(self, ex),
133         }
134     }
135
136     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
137         NestedVisitorMap::None
138     }
139 }