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
;
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.
19 let mut var_visitor
= VarCollectorVisitor
{
21 ids
: HirIdSet
::default(),
22 def_ids
: DefIdMap
::default(),
25 var_visitor
.visit_expr(cond
);
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
)
38 let mutable_static_in_cond
= var_visitor
.def_ids
.iter().any(|(_
, v
)| *v
);
40 let mut has_break_or_return_visitor
= HasBreakOrReturnVisitor
{
41 has_break_or_return
: false,
43 has_break_or_return_visitor
.visit_expr(expr
);
44 let has_break_or_return
= has_break_or_return_visitor
.has_break_or_return
;
46 if no_cond_variable_mutated
&& !mutable_static_in_cond
{
49 WHILE_IMMUTABLE_CONDITION
,
51 "variables in the condition are not mutated in the loop body",
53 diag
.note("this may lead to an infinite or to a never running loop");
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 { } }`");
64 struct HasBreakOrReturnVisitor
{
65 has_break_or_return
: bool
,
68 impl<'tcx
> Visitor
<'tcx
> for HasBreakOrReturnVisitor
{
69 fn visit_expr(&mut self, expr
: &'tcx Expr
<'_
>) {
70 if self.has_break_or_return
{
75 ExprKind
::Ret(_
) | ExprKind
::Break(_
, _
) => {
76 self.has_break_or_return
= true;
82 walk_expr(self, expr
);
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
>,
93 def_ids
: DefIdMap
<bool
>,
97 impl<'a
, 'tcx
> VarCollectorVisitor
<'a
, 'tcx
> {
98 fn insert_def_id(&mut self, ex
: &'tcx Expr
<'_
>) {
100 if let ExprKind
::Path(ref qpath
) = ex
.kind
;
101 if let QPath
::Resolved(None
, _
) = *qpath
;
103 match self.cx
.qpath_res(qpath
, ex
.hir_id
) {
104 Res
::Local(hir_id
) => {
105 self.ids
.insert(hir_id
);
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
);
118 impl<'a
, 'tcx
> Visitor
<'tcx
> for VarCollectorVisitor
<'a
, 'tcx
> {
119 fn visit_expr(&mut self, ex
: &'tcx Expr
<'_
>) {
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,
125 _
=> walk_expr(self, ex
),