2021-03-12 08:30:50 -06:00
|
|
|
use super::WHILE_IMMUTABLE_CONDITION;
|
2021-06-03 01:41:37 -05:00
|
|
|
use clippy_utils::consts::constant;
|
2021-03-25 13:29:11 -05:00
|
|
|
use clippy_utils::diagnostics::span_lint_and_then;
|
|
|
|
use clippy_utils::usage::mutated_variables;
|
2021-03-12 08:30:50 -06:00
|
|
|
use if_chain::if_chain;
|
|
|
|
use rustc_hir::def::{DefKind, Res};
|
2021-04-08 10:50:13 -05:00
|
|
|
use rustc_hir::def_id::DefIdMap;
|
2022-01-15 16:07:52 -06:00
|
|
|
use rustc_hir::intravisit::{walk_expr, Visitor};
|
2021-04-08 10:50:13 -05:00
|
|
|
use rustc_hir::HirIdSet;
|
|
|
|
use rustc_hir::{Expr, ExprKind, QPath};
|
2021-03-12 08:30:50 -06:00
|
|
|
use rustc_lint::LateContext;
|
|
|
|
|
|
|
|
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) {
|
|
|
|
if constant(cx, cx.typeck_results(), cond).is_some() {
|
|
|
|
// A pure constant condition (e.g., `while false`) is not linted.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut var_visitor = VarCollectorVisitor {
|
|
|
|
cx,
|
2021-04-08 10:50:13 -05:00
|
|
|
ids: HirIdSet::default(),
|
|
|
|
def_ids: DefIdMap::default(),
|
2021-03-12 08:30:50 -06:00
|
|
|
skip: false,
|
|
|
|
};
|
|
|
|
var_visitor.visit_expr(cond);
|
|
|
|
if var_visitor.skip {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let used_in_condition = &var_visitor.ids;
|
2021-05-06 04:51:22 -05:00
|
|
|
let mutated_in_body = mutated_variables(expr, cx);
|
|
|
|
let mutated_in_condition = mutated_variables(cond, cx);
|
|
|
|
let no_cond_variable_mutated =
|
|
|
|
if let (Some(used_mutably_body), Some(used_mutably_cond)) = (mutated_in_body, mutated_in_condition) {
|
|
|
|
used_in_condition.is_disjoint(&used_mutably_body) && used_in_condition.is_disjoint(&used_mutably_cond)
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
};
|
2021-03-12 08:30:50 -06:00
|
|
|
let mutable_static_in_cond = var_visitor.def_ids.iter().any(|(_, v)| *v);
|
|
|
|
|
|
|
|
let mut has_break_or_return_visitor = HasBreakOrReturnVisitor {
|
|
|
|
has_break_or_return: false,
|
|
|
|
};
|
|
|
|
has_break_or_return_visitor.visit_expr(expr);
|
|
|
|
let has_break_or_return = has_break_or_return_visitor.has_break_or_return;
|
|
|
|
|
|
|
|
if no_cond_variable_mutated && !mutable_static_in_cond {
|
|
|
|
span_lint_and_then(
|
|
|
|
cx,
|
|
|
|
WHILE_IMMUTABLE_CONDITION,
|
|
|
|
cond.span,
|
|
|
|
"variables in the condition are not mutated in the loop body",
|
|
|
|
|diag| {
|
|
|
|
diag.note("this may lead to an infinite or to a never running loop");
|
|
|
|
|
|
|
|
if has_break_or_return {
|
|
|
|
diag.note("this loop contains `return`s or `break`s");
|
|
|
|
diag.help("rewrite it as `if cond { loop { } }`");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct HasBreakOrReturnVisitor {
|
|
|
|
has_break_or_return: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'tcx> Visitor<'tcx> for HasBreakOrReturnVisitor {
|
|
|
|
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
|
|
|
if self.has_break_or_return {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
match expr.kind {
|
|
|
|
ExprKind::Ret(_) | ExprKind::Break(_, _) => {
|
|
|
|
self.has_break_or_return = true;
|
|
|
|
return;
|
|
|
|
},
|
|
|
|
_ => {},
|
|
|
|
}
|
|
|
|
|
|
|
|
walk_expr(self, expr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Collects the set of variables in an expression
|
|
|
|
/// Stops analysis if a function call is found
|
|
|
|
/// Note: In some cases such as `self`, there are no mutable annotation,
|
|
|
|
/// All variables definition IDs are collected
|
|
|
|
struct VarCollectorVisitor<'a, 'tcx> {
|
|
|
|
cx: &'a LateContext<'tcx>,
|
2021-04-08 10:50:13 -05:00
|
|
|
ids: HirIdSet,
|
|
|
|
def_ids: DefIdMap<bool>,
|
2021-03-12 08:30:50 -06:00
|
|
|
skip: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> {
|
|
|
|
fn insert_def_id(&mut self, ex: &'tcx Expr<'_>) {
|
|
|
|
if_chain! {
|
|
|
|
if let ExprKind::Path(ref qpath) = ex.kind;
|
|
|
|
if let QPath::Resolved(None, _) = *qpath;
|
|
|
|
then {
|
2021-04-08 10:50:13 -05:00
|
|
|
match self.cx.qpath_res(qpath, ex.hir_id) {
|
2021-03-12 08:30:50 -06:00
|
|
|
Res::Local(hir_id) => {
|
|
|
|
self.ids.insert(hir_id);
|
|
|
|
},
|
|
|
|
Res::Def(DefKind::Static, def_id) => {
|
|
|
|
let mutable = self.cx.tcx.is_mutable_static(def_id);
|
|
|
|
self.def_ids.insert(def_id, mutable);
|
|
|
|
},
|
|
|
|
_ => {},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, 'tcx> Visitor<'tcx> for VarCollectorVisitor<'a, 'tcx> {
|
|
|
|
fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
|
|
|
|
match ex.kind {
|
|
|
|
ExprKind::Path(_) => self.insert_def_id(ex),
|
|
|
|
// If there is any function/method call… we just stop analysis
|
|
|
|
ExprKind::Call(..) | ExprKind::MethodCall(..) => self.skip = true,
|
|
|
|
|
|
|
|
_ => walk_expr(self, ex),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|