2021-03-12 08:30:50 -06:00
|
|
|
use super::utils::{LoopNestVisitor, Nesting};
|
|
|
|
use super::WHILE_LET_ON_ITERATOR;
|
2021-03-25 13:29:11 -05:00
|
|
|
use clippy_utils::diagnostics::span_lint_and_sugg;
|
|
|
|
use clippy_utils::source::snippet_with_applicability;
|
|
|
|
use clippy_utils::ty::implements_trait;
|
|
|
|
use clippy_utils::usage::mutated_variables;
|
|
|
|
use clippy_utils::{
|
|
|
|
get_enclosing_block, is_refutable, is_trait_method, last_path_segment, path_to_local, path_to_local_id,
|
2021-03-12 08:30:50 -06:00
|
|
|
};
|
|
|
|
use if_chain::if_chain;
|
|
|
|
use rustc_errors::Applicability;
|
|
|
|
use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor};
|
|
|
|
use rustc_hir::{Expr, ExprKind, HirId, MatchSource, Node, PatKind};
|
|
|
|
use rustc_lint::LateContext;
|
|
|
|
use rustc_middle::hir::map::Map;
|
|
|
|
use rustc_span::symbol::sym;
|
|
|
|
|
|
|
|
pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
2021-04-08 10:50:13 -05:00
|
|
|
if let ExprKind::Match(match_expr, arms, MatchSource::WhileLetDesugar) = expr.kind {
|
2021-03-12 08:30:50 -06:00
|
|
|
let pat = &arms[0].pat.kind;
|
2021-04-08 10:50:13 -05:00
|
|
|
if let (&PatKind::TupleStruct(ref qpath, pat_args, _), &ExprKind::MethodCall(method_path, _, method_args, _)) =
|
|
|
|
(pat, &match_expr.kind)
|
2021-03-12 08:30:50 -06:00
|
|
|
{
|
|
|
|
let iter_expr = &method_args[0];
|
|
|
|
|
|
|
|
// Don't lint when the iterator is recreated on every iteration
|
|
|
|
if_chain! {
|
|
|
|
if let ExprKind::MethodCall(..) | ExprKind::Call(..) = iter_expr.kind;
|
2021-03-25 13:29:11 -05:00
|
|
|
if let Some(iter_def_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
|
2021-03-12 08:30:50 -06:00
|
|
|
if implements_trait(cx, cx.typeck_results().expr_ty(iter_expr), iter_def_id, &[]);
|
|
|
|
then {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let lhs_constructor = last_path_segment(qpath);
|
|
|
|
if method_path.ident.name == sym::next
|
2021-03-25 13:29:11 -05:00
|
|
|
&& is_trait_method(cx, match_expr, sym::Iterator)
|
2021-03-12 08:30:50 -06:00
|
|
|
&& lhs_constructor.ident.name == sym::Some
|
|
|
|
&& (pat_args.is_empty()
|
2021-04-08 10:50:13 -05:00
|
|
|
|| !is_refutable(cx, pat_args[0])
|
|
|
|
&& !is_used_inside(cx, iter_expr, arms[0].body)
|
2021-03-12 08:30:50 -06:00
|
|
|
&& !is_iterator_used_after_while_let(cx, iter_expr)
|
|
|
|
&& !is_nested(cx, expr, &method_args[0]))
|
|
|
|
{
|
|
|
|
let mut applicability = Applicability::MachineApplicable;
|
|
|
|
let iterator = snippet_with_applicability(cx, method_args[0].span, "_", &mut applicability);
|
|
|
|
let loop_var = if pat_args.is_empty() {
|
|
|
|
"_".to_string()
|
|
|
|
} else {
|
|
|
|
snippet_with_applicability(cx, pat_args[0].span, "_", &mut applicability).into_owned()
|
|
|
|
};
|
|
|
|
span_lint_and_sugg(
|
|
|
|
cx,
|
|
|
|
WHILE_LET_ON_ITERATOR,
|
|
|
|
expr.span.with_hi(match_expr.span.hi()),
|
|
|
|
"this loop could be written as a `for` loop",
|
|
|
|
"try",
|
|
|
|
format!("for {} in {}", loop_var, iterator),
|
|
|
|
applicability,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_used_inside<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, container: &'tcx Expr<'_>) -> bool {
|
|
|
|
let def_id = match path_to_local(expr) {
|
|
|
|
Some(id) => id,
|
|
|
|
None => return false,
|
|
|
|
};
|
|
|
|
if let Some(used_mutably) = mutated_variables(container, cx) {
|
|
|
|
if used_mutably.contains(&def_id) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_iterator_used_after_while_let<'tcx>(cx: &LateContext<'tcx>, iter_expr: &'tcx Expr<'_>) -> bool {
|
|
|
|
let def_id = match path_to_local(iter_expr) {
|
|
|
|
Some(id) => id,
|
|
|
|
None => return false,
|
|
|
|
};
|
|
|
|
let mut visitor = VarUsedAfterLoopVisitor {
|
|
|
|
def_id,
|
|
|
|
iter_expr_id: iter_expr.hir_id,
|
|
|
|
past_while_let: false,
|
|
|
|
var_used_after_while_let: false,
|
|
|
|
};
|
|
|
|
if let Some(enclosing_block) = get_enclosing_block(cx, def_id) {
|
|
|
|
walk_block(&mut visitor, enclosing_block);
|
|
|
|
}
|
|
|
|
visitor.var_used_after_while_let
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_nested(cx: &LateContext<'_>, match_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool {
|
|
|
|
if_chain! {
|
|
|
|
if let Some(loop_block) = get_enclosing_block(cx, match_expr.hir_id);
|
|
|
|
let parent_node = cx.tcx.hir().get_parent_node(loop_block.hir_id);
|
|
|
|
if let Some(Node::Expr(loop_expr)) = cx.tcx.hir().find(parent_node);
|
|
|
|
then {
|
|
|
|
return is_loop_nested(cx, loop_expr, iter_expr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_loop_nested(cx: &LateContext<'_>, loop_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool {
|
|
|
|
let mut id = loop_expr.hir_id;
|
|
|
|
let iter_id = if let Some(id) = path_to_local(iter_expr) {
|
|
|
|
id
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
loop {
|
|
|
|
let parent = cx.tcx.hir().get_parent_node(id);
|
|
|
|
if parent == id {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
match cx.tcx.hir().find(parent) {
|
|
|
|
Some(Node::Expr(expr)) => {
|
|
|
|
if let ExprKind::Loop(..) = expr.kind {
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
},
|
|
|
|
Some(Node::Block(block)) => {
|
|
|
|
let mut block_visitor = LoopNestVisitor {
|
|
|
|
hir_id: id,
|
|
|
|
iterator: iter_id,
|
|
|
|
nesting: Nesting::Unknown,
|
|
|
|
};
|
|
|
|
walk_block(&mut block_visitor, block);
|
|
|
|
if block_visitor.nesting == Nesting::RuledOut {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Some(Node::Stmt(_)) => (),
|
|
|
|
_ => {
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
}
|
|
|
|
id = parent;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct VarUsedAfterLoopVisitor {
|
|
|
|
def_id: HirId,
|
|
|
|
iter_expr_id: HirId,
|
|
|
|
past_while_let: bool,
|
|
|
|
var_used_after_while_let: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'tcx> Visitor<'tcx> for VarUsedAfterLoopVisitor {
|
|
|
|
type Map = Map<'tcx>;
|
|
|
|
|
|
|
|
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
|
|
|
if self.past_while_let {
|
|
|
|
if path_to_local_id(expr, self.def_id) {
|
|
|
|
self.var_used_after_while_let = true;
|
|
|
|
}
|
|
|
|
} else if self.iter_expr_id == expr.hir_id {
|
|
|
|
self.past_while_let = true;
|
|
|
|
}
|
|
|
|
walk_expr(self, expr);
|
|
|
|
}
|
|
|
|
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
|
|
|
|
NestedVisitorMap::None
|
|
|
|
}
|
|
|
|
}
|