]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/loops/while_immutable_condition.rs
New upstream version 1.52.1+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / loops / while_immutable_condition.rs
CommitLineData
f20569fa
XL
1use super::WHILE_IMMUTABLE_CONDITION;
2use crate::consts::constant;
3use crate::utils::span_lint_and_then;
4use crate::utils::usage::mutated_variables;
5use if_chain::if_chain;
6use rustc_data_structures::fx::{FxHashMap, FxHashSet};
7use rustc_hir::def::{DefKind, Res};
8use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
9use rustc_hir::{def_id, Expr, ExprKind, HirId, QPath};
10use rustc_lint::LateContext;
11use rustc_middle::hir::map::Map;
12use std::iter::Iterator;
13
14pub(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
62struct HasBreakOrReturnVisitor {
63 has_break_or_return: bool,
64}
65
66impl<'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
94struct 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
101impl<'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
123impl<'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}