1 use clippy_utils::ty::{has_iter_method, implements_trait};
2 use clippy_utils::{get_parent_expr, is_integer_const, path_to_local, path_to_local_id, sugg};
3 use if_chain::if_chain;
4 use rustc_errors::Applicability;
5 use rustc_hir::intravisit::{walk_expr, walk_pat, walk_stmt, NestedVisitorMap, Visitor};
6 use rustc_hir::HirIdMap;
7 use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Stmt, StmtKind};
8 use rustc_lint::LateContext;
9 use rustc_middle::hir::map::Map;
10 use rustc_span::source_map::Span;
11 use rustc_span::symbol::{sym, Symbol};
12 use std::iter::Iterator;
14 #[derive(Debug, PartialEq)]
15 enum IncrementVisitorVarState {
16 Initial, // Not examined yet
17 IncrOnce, // Incremented exactly once, may be a loop counter
21 /// Scan a for loop for variables that are incremented exactly once and not used after that.
22 pub(super) struct IncrementVisitor<'a, 'tcx> {
23 cx: &'a LateContext<'tcx>, // context reference
24 states: HirIdMap<IncrementVisitorVarState>, // incremented variables
25 depth: u32, // depth of conditional expressions
29 impl<'a, 'tcx> IncrementVisitor<'a, 'tcx> {
30 pub(super) fn new(cx: &'a LateContext<'tcx>) -> Self {
33 states: HirIdMap::default(),
39 pub(super) fn into_results(self) -> impl Iterator<Item = HirId> {
40 self.states.into_iter().filter_map(|(id, state)| {
41 if state == IncrementVisitorVarState::IncrOnce {
50 impl<'a, 'tcx> Visitor<'tcx> for IncrementVisitor<'a, 'tcx> {
53 fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
58 // If node is a variable
59 if let Some(def_id) = path_to_local(expr) {
60 if let Some(parent) = get_parent_expr(self.cx, expr) {
61 let state = self.states.entry(def_id).or_insert(IncrementVisitorVarState::Initial);
62 if *state == IncrementVisitorVarState::IncrOnce {
63 *state = IncrementVisitorVarState::DontWarn;
68 ExprKind::AssignOp(op, lhs, rhs) => {
69 if lhs.hir_id == expr.hir_id {
70 *state = if op.node == BinOpKind::Add
71 && is_integer_const(self.cx, rhs, 1)
72 && *state == IncrementVisitorVarState::Initial
75 IncrementVisitorVarState::IncrOnce
77 // Assigned some other value or assigned multiple times
78 IncrementVisitorVarState::DontWarn
82 ExprKind::Assign(lhs, _, _) if lhs.hir_id == expr.hir_id => {
83 *state = IncrementVisitorVarState::DontWarn;
85 ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => {
86 *state = IncrementVisitorVarState::DontWarn;
92 walk_expr(self, expr);
93 } else if is_loop(expr) || is_conditional(expr) {
95 walk_expr(self, expr);
97 } else if let ExprKind::Continue(_) = expr.kind {
100 walk_expr(self, expr);
103 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
104 NestedVisitorMap::None
108 enum InitializeVisitorState<'hir> {
109 Initial, // Not examined yet
110 Declared(Symbol), // Declared but not (yet) initialized
113 initializer: &'hir Expr<'hir>,
118 /// Checks whether a variable is initialized at the start of a loop and not modified
119 /// and used after the loop.
120 pub(super) struct InitializeVisitor<'a, 'tcx> {
121 cx: &'a LateContext<'tcx>, // context reference
122 end_expr: &'tcx Expr<'tcx>, // the for loop. Stop scanning here.
124 state: InitializeVisitorState<'tcx>,
125 depth: u32, // depth of conditional expressions
129 impl<'a, 'tcx> InitializeVisitor<'a, 'tcx> {
130 pub(super) fn new(cx: &'a LateContext<'tcx>, end_expr: &'tcx Expr<'tcx>, var_id: HirId) -> Self {
135 state: InitializeVisitorState::Initial,
141 pub(super) fn get_result(&self) -> Option<(Symbol, &'tcx Expr<'tcx>)> {
142 if let InitializeVisitorState::Initialized { name, initializer } = self.state {
143 Some((name, initializer))
150 impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> {
151 type Map = Map<'tcx>;
153 fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) {
154 // Look for declarations of the variable
156 if let StmtKind::Local(local) = stmt.kind;
157 if local.pat.hir_id == self.var_id;
158 if let PatKind::Binding(.., ident, _) = local.pat.kind;
160 self.state = local.init.map_or(InitializeVisitorState::Declared(ident.name), |init| {
161 InitializeVisitorState::Initialized {
168 walk_stmt(self, stmt);
171 fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
172 if matches!(self.state, InitializeVisitorState::DontWarn) {
175 if expr.hir_id == self.end_expr.hir_id {
176 self.past_loop = true;
179 // No need to visit expressions before the variable is
181 if matches!(self.state, InitializeVisitorState::Initial) {
185 // If node is the desired variable, see how it's used
186 if path_to_local_id(expr, self.var_id) {
188 self.state = InitializeVisitorState::DontWarn;
192 if let Some(parent) = get_parent_expr(self.cx, expr) {
194 ExprKind::AssignOp(_, lhs, _) if lhs.hir_id == expr.hir_id => {
195 self.state = InitializeVisitorState::DontWarn;
197 ExprKind::Assign(lhs, rhs, _) if lhs.hir_id == expr.hir_id => {
198 self.state = if_chain! {
200 if let InitializeVisitorState::Declared(name)
201 | InitializeVisitorState::Initialized { name, ..} = self.state;
203 InitializeVisitorState::Initialized { initializer: rhs, name }
205 InitializeVisitorState::DontWarn
209 ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => {
210 self.state = InitializeVisitorState::DontWarn;
216 walk_expr(self, expr);
217 } else if !self.past_loop && is_loop(expr) {
218 self.state = InitializeVisitorState::DontWarn;
219 } else if is_conditional(expr) {
221 walk_expr(self, expr);
224 walk_expr(self, expr);
228 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
229 NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
233 fn is_loop(expr: &Expr<'_>) -> bool {
234 matches!(expr.kind, ExprKind::Loop(..))
237 fn is_conditional(expr: &Expr<'_>) -> bool {
238 matches!(expr.kind, ExprKind::If(..) | ExprKind::Match(..))
241 #[derive(PartialEq, Eq)]
242 pub(super) enum Nesting {
243 Unknown, // no nesting detected yet
244 RuledOut, // the iterator is initialized or assigned within scope
245 LookFurther, // no nesting detected, no further walk required
248 use self::Nesting::{LookFurther, RuledOut, Unknown};
250 pub(super) struct LoopNestVisitor {
251 pub(super) hir_id: HirId,
252 pub(super) iterator: HirId,
253 pub(super) nesting: Nesting,
256 impl<'tcx> Visitor<'tcx> for LoopNestVisitor {
257 type Map = Map<'tcx>;
259 fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) {
260 if stmt.hir_id == self.hir_id {
261 self.nesting = LookFurther;
262 } else if self.nesting == Unknown {
263 walk_stmt(self, stmt);
267 fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
268 if self.nesting != Unknown {
271 if expr.hir_id == self.hir_id {
272 self.nesting = LookFurther;
276 ExprKind::Assign(path, _, _) | ExprKind::AssignOp(_, path, _) => {
277 if path_to_local_id(path, self.iterator) {
278 self.nesting = RuledOut;
281 _ => walk_expr(self, expr),
285 fn visit_pat(&mut self, pat: &'tcx Pat<'_>) {
286 if self.nesting != Unknown {
289 if let PatKind::Binding(_, id, ..) = pat.kind {
290 if id == self.iterator {
291 self.nesting = RuledOut;
298 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
299 NestedVisitorMap::None
303 // this function assumes the given expression is a `for` loop.
304 pub(super) fn get_span_of_entire_for_loop(expr: &Expr<'_>) -> Span {
305 // for some reason this is the only way to get the `Span`
306 // of the entire `for` loop
307 if let ExprKind::Match(_, arms, _) = &expr.kind {
314 /// If `arg` was the argument to a `for` loop, return the "cleanest" way of writing the
315 /// actual `Iterator` that the loop uses.
316 pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic_ref: &mut Applicability) -> String {
317 let impls_iterator = cx.tcx.get_diagnostic_item(sym::Iterator).map_or(false, |id| {
318 implements_trait(cx, cx.typeck_results().expr_ty(arg), id, &[])
323 sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par()
326 // (&x).into_iter() ==> x.iter()
327 // (&mut x).into_iter() ==> x.iter_mut()
329 ExprKind::AddrOf(BorrowKind::Ref, mutability, arg_inner)
330 if has_iter_method(cx, cx.typeck_results().expr_ty(arg_inner)).is_some() =>
332 let meth_name = match mutability {
333 Mutability::Mut => "iter_mut",
334 Mutability::Not => "iter",
338 sugg::Sugg::hir_with_applicability(cx, arg_inner, "_", applic_ref).maybe_par(),
344 sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par()