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