]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/loops/while_immutable_condition.rs
New upstream version 1.60.0+dfsg1
[rustc.git] / src / tools / clippy / 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, Visitor};
9 use rustc_hir::HirIdSet;
10 use rustc_hir::{Expr, ExprKind, QPath};
11 use rustc_lint::LateContext;
12
13 pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) {
14 if constant(cx, cx.typeck_results(), cond).is_some() {
15 // A pure constant condition (e.g., `while false`) is not linted.
16 return;
17 }
18
19 let mut var_visitor = VarCollectorVisitor {
20 cx,
21 ids: HirIdSet::default(),
22 def_ids: DefIdMap::default(),
23 skip: false,
24 };
25 var_visitor.visit_expr(cond);
26 if var_visitor.skip {
27 return;
28 }
29 let used_in_condition = &var_visitor.ids;
30 let mutated_in_body = mutated_variables(expr, cx);
31 let mutated_in_condition = mutated_variables(cond, cx);
32 let no_cond_variable_mutated =
33 if let (Some(used_mutably_body), Some(used_mutably_cond)) = (mutated_in_body, mutated_in_condition) {
34 used_in_condition.is_disjoint(&used_mutably_body) && used_in_condition.is_disjoint(&used_mutably_cond)
35 } else {
36 return;
37 };
38 let mutable_static_in_cond = var_visitor.def_ids.iter().any(|(_, v)| *v);
39
40 let mut has_break_or_return_visitor = HasBreakOrReturnVisitor {
41 has_break_or_return: false,
42 };
43 has_break_or_return_visitor.visit_expr(expr);
44 let has_break_or_return = has_break_or_return_visitor.has_break_or_return;
45
46 if no_cond_variable_mutated && !mutable_static_in_cond {
47 span_lint_and_then(
48 cx,
49 WHILE_IMMUTABLE_CONDITION,
50 cond.span,
51 "variables in the condition are not mutated in the loop body",
52 |diag| {
53 diag.note("this may lead to an infinite or to a never running loop");
54
55 if has_break_or_return {
56 diag.note("this loop contains `return`s or `break`s");
57 diag.help("rewrite it as `if cond { loop { } }`");
58 }
59 },
60 );
61 }
62 }
63
64 struct HasBreakOrReturnVisitor {
65 has_break_or_return: bool,
66 }
67
68 impl<'tcx> Visitor<'tcx> for HasBreakOrReturnVisitor {
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
86 /// Collects the set of variables in an expression
87 /// Stops analysis if a function call is found
88 /// Note: In some cases such as `self`, there are no mutable annotation,
89 /// All variables definition IDs are collected
90 struct VarCollectorVisitor<'a, 'tcx> {
91 cx: &'a LateContext<'tcx>,
92 ids: HirIdSet,
93 def_ids: DefIdMap<bool>,
94 skip: bool,
95 }
96
97 impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> {
98 fn insert_def_id(&mut self, ex: &'tcx Expr<'_>) {
99 if_chain! {
100 if let ExprKind::Path(ref qpath) = ex.kind;
101 if let QPath::Resolved(None, _) = *qpath;
102 then {
103 match self.cx.qpath_res(qpath, ex.hir_id) {
104 Res::Local(hir_id) => {
105 self.ids.insert(hir_id);
106 },
107 Res::Def(DefKind::Static, def_id) => {
108 let mutable = self.cx.tcx.is_mutable_static(def_id);
109 self.def_ids.insert(def_id, mutable);
110 },
111 _ => {},
112 }
113 }
114 }
115 }
116 }
117
118 impl<'a, 'tcx> Visitor<'tcx> for VarCollectorVisitor<'a, 'tcx> {
119 fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
120 match ex.kind {
121 ExprKind::Path(_) => self.insert_def_id(ex),
122 // If there is any function/method call… we just stop analysis
123 ExprKind::Call(..) | ExprKind::MethodCall(..) => self.skip = true,
124
125 _ => walk_expr(self, ex),
126 }
127 }
128 }