| use clippy_utils::res::MaybeResPath; | |
| use clippy_utils::ty::{has_iter_method, implements_trait}; | |
| use clippy_utils::{get_parent_expr, is_integer_const, sugg}; | |
| use rustc_ast::ast::{LitIntType, LitKind}; | |
| use rustc_errors::Applicability; | |
| use rustc_hir::intravisit::{Visitor, walk_expr, walk_local}; | |
| use rustc_hir::{AssignOpKind, BorrowKind, Expr, ExprKind, HirId, HirIdMap, LetStmt, Mutability, PatKind}; | |
| use rustc_lint::LateContext; | |
| use rustc_middle::hir::nested_filter; | |
| use rustc_middle::ty::{self, Ty}; | |
| use rustc_span::Spanned; | |
| use rustc_span::symbol::{Symbol, sym}; | |
| #[derive(Debug, PartialEq, Eq)] | |
| enum IncrementVisitorVarState { | |
| Initial, // Not examined yet | |
| IncrOnce, // Incremented exactly once, may be a loop counter | |
| DontWarn, | |
| } | |
| /// Scan a for loop for variables that are incremented exactly once and not used after that. | |
| pub(super) struct IncrementVisitor<'a, 'tcx> { | |
| cx: &'a LateContext<'tcx>, // context reference | |
| states: HirIdMap<IncrementVisitorVarState>, // incremented variables | |
| depth: u32, // depth of conditional expressions | |
| } | |
| impl<'a, 'tcx> IncrementVisitor<'a, 'tcx> { | |
| pub(super) fn new(cx: &'a LateContext<'tcx>) -> Self { | |
| Self { | |
| cx, | |
| states: HirIdMap::default(), | |
| depth: 0, | |
| } | |
| } | |
| pub(super) fn into_results(self) -> impl Iterator<Item = HirId> { | |
| self.states.into_iter().filter_map(|(id, state)| { | |
| if state == IncrementVisitorVarState::IncrOnce { | |
| Some(id) | |
| } else { | |
| None | |
| } | |
| }) | |
| } | |
| } | |
| impl<'tcx> Visitor<'tcx> for IncrementVisitor<'_, 'tcx> { | |
| fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { | |
| // If node is a variable | |
| if let Some(def_id) = expr.res_local_id() { | |
| if let Some(parent) = get_parent_expr(self.cx, expr) { | |
| let state = self.states.entry(def_id).or_insert(IncrementVisitorVarState::Initial); | |
| if *state == IncrementVisitorVarState::IncrOnce { | |
| *state = IncrementVisitorVarState::DontWarn; | |
| return; | |
| } | |
| match parent.kind { | |
| ExprKind::AssignOp(op, lhs, rhs) if lhs.hir_id == expr.hir_id => { | |
| *state = if op.node == AssignOpKind::AddAssign | |
| && is_integer_const(self.cx, rhs, 1) | |
| && *state == IncrementVisitorVarState::Initial | |
| && self.depth == 0 | |
| { | |
| IncrementVisitorVarState::IncrOnce | |
| } else { | |
| // Assigned some other value or assigned multiple times | |
| IncrementVisitorVarState::DontWarn | |
| }; | |
| }, | |
| ExprKind::Assign(lhs, _, _) if lhs.hir_id == expr.hir_id => { | |
| *state = IncrementVisitorVarState::DontWarn; | |
| }, | |
| ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) => { | |
| *state = IncrementVisitorVarState::DontWarn; | |
| }, | |
| _ => (), | |
| } | |
| } | |
| walk_expr(self, expr); | |
| } else if is_loop(expr) || is_conditional(expr) { | |
| self.depth += 1; | |
| walk_expr(self, expr); | |
| self.depth -= 1; | |
| } else if let ExprKind::Continue(_) = expr.kind { | |
| // If we see a `continue` block, then we increment depth so that the IncrementVisitor | |
| // state will be set to DontWarn if we see the variable being modified anywhere afterwards. | |
| self.depth += 1; | |
| } else { | |
| walk_expr(self, expr); | |
| } | |
| } | |
| } | |
| enum InitializeVisitorState<'hir> { | |
| Initial, // Not examined yet | |
| Declared(Symbol, Option<Ty<'hir>>), // Declared but not (yet) initialized | |
| Initialized { | |
| name: Symbol, | |
| ty: Option<Ty<'hir>>, | |
| initializer: &'hir Expr<'hir>, | |
| }, | |
| DontWarn, | |
| } | |
| /// Checks whether a variable is initialized at the start of a loop and not modified | |
| /// and used after the loop. | |
| pub(super) struct InitializeVisitor<'a, 'tcx> { | |
| cx: &'a LateContext<'tcx>, // context reference | |
| end_expr: &'tcx Expr<'tcx>, // the for loop. Stop scanning here. | |
| var_id: HirId, | |
| state: InitializeVisitorState<'tcx>, | |
| depth: u32, // depth of conditional expressions | |
| past_loop: bool, | |
| } | |
| impl<'a, 'tcx> InitializeVisitor<'a, 'tcx> { | |
| pub(super) fn new(cx: &'a LateContext<'tcx>, end_expr: &'tcx Expr<'tcx>, var_id: HirId) -> Self { | |
| Self { | |
| cx, | |
| end_expr, | |
| var_id, | |
| state: InitializeVisitorState::Initial, | |
| depth: 0, | |
| past_loop: false, | |
| } | |
| } | |
| pub(super) fn get_result(&self) -> Option<(Symbol, Option<Ty<'tcx>>, &'tcx Expr<'tcx>)> { | |
| if let InitializeVisitorState::Initialized { name, ty, initializer } = self.state { | |
| Some((name, ty, initializer)) | |
| } else { | |
| None | |
| } | |
| } | |
| } | |
| impl<'tcx> Visitor<'tcx> for InitializeVisitor<'_, 'tcx> { | |
| type NestedFilter = nested_filter::OnlyBodies; | |
| fn visit_local(&mut self, l: &'tcx LetStmt<'_>) { | |
| // Look for declarations of the variable | |
| if l.pat.hir_id == self.var_id | |
| && let PatKind::Binding(.., ident, _) = l.pat.kind | |
| { | |
| let ty = l.ty.map(|_| self.cx.typeck_results().pat_ty(l.pat)); | |
| self.state = l.init.map_or(InitializeVisitorState::Declared(ident.name, ty), |init| { | |
| InitializeVisitorState::Initialized { | |
| initializer: init, | |
| ty, | |
| name: ident.name, | |
| } | |
| }); | |
| } | |
| walk_local(self, l); | |
| } | |
| fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { | |
| if matches!(self.state, InitializeVisitorState::DontWarn) { | |
| return; | |
| } | |
| if expr.hir_id == self.end_expr.hir_id { | |
| self.past_loop = true; | |
| return; | |
| } | |
| // No need to visit expressions before the variable is | |
| // declared | |
| if matches!(self.state, InitializeVisitorState::Initial) { | |
| return; | |
| } | |
| // If node is the desired variable, see how it's used | |
| if expr.res_local_id() == Some(self.var_id) { | |
| if self.past_loop { | |
| self.state = InitializeVisitorState::DontWarn; | |
| return; | |
| } | |
| if let Some(parent) = get_parent_expr(self.cx, expr) { | |
| match parent.kind { | |
| ExprKind::AssignOp(_, lhs, _) if lhs.hir_id == expr.hir_id => { | |
| self.state = InitializeVisitorState::DontWarn; | |
| }, | |
| ExprKind::Assign(lhs, rhs, _) if lhs.hir_id == expr.hir_id => { | |
| self.state = if self.depth == 0 { | |
| match self.state { | |
| InitializeVisitorState::Declared(name, mut ty) => { | |
| if ty.is_none() { | |
| if let ExprKind::Lit(Spanned { | |
| node: LitKind::Int(_, LitIntType::Unsuffixed), | |
| .. | |
| }) = rhs.kind | |
| { | |
| ty = None; | |
| } else { | |
| ty = self.cx.typeck_results().expr_ty_opt(rhs); | |
| } | |
| } | |
| InitializeVisitorState::Initialized { | |
| initializer: rhs, | |
| ty, | |
| name, | |
| } | |
| }, | |
| InitializeVisitorState::Initialized { ty, name, .. } => { | |
| InitializeVisitorState::Initialized { | |
| initializer: rhs, | |
| ty, | |
| name, | |
| } | |
| }, | |
| _ => InitializeVisitorState::DontWarn, | |
| } | |
| } else { | |
| InitializeVisitorState::DontWarn | |
| } | |
| }, | |
| ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) => { | |
| self.state = InitializeVisitorState::DontWarn; | |
| }, | |
| _ => (), | |
| } | |
| } | |
| walk_expr(self, expr); | |
| } else if !self.past_loop && is_loop(expr) { | |
| self.state = InitializeVisitorState::DontWarn; | |
| } else if is_conditional(expr) { | |
| self.depth += 1; | |
| walk_expr(self, expr); | |
| self.depth -= 1; | |
| } else { | |
| walk_expr(self, expr); | |
| } | |
| } | |
| fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { | |
| self.cx.tcx | |
| } | |
| } | |
| fn is_loop(expr: &Expr<'_>) -> bool { | |
| matches!(expr.kind, ExprKind::Loop(..)) | |
| } | |
| fn is_conditional(expr: &Expr<'_>) -> bool { | |
| matches!(expr.kind, ExprKind::If(..) | ExprKind::Match(..)) | |
| } | |
| /// If `arg` was the argument to a `for` loop, return the "cleanest" way of writing the | |
| /// actual `Iterator` that the loop uses. | |
| pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applicability: &mut Applicability) -> String { | |
| let impls_iterator = cx | |
| .tcx | |
| .get_diagnostic_item(sym::Iterator) | |
| .is_some_and(|id| implements_trait(cx, cx.typeck_results().expr_ty(arg), id, &[])); | |
| if impls_iterator { | |
| format!( | |
| "{}", | |
| sugg::Sugg::hir_with_applicability(cx, arg, "_", applicability).maybe_paren() | |
| ) | |
| } else { | |
| // (&x).into_iter() ==> x.iter() | |
| // (&mut x).into_iter() ==> x.iter_mut() | |
| let arg_ty = cx.typeck_results().expr_ty_adjusted(arg); | |
| match &arg_ty.kind() { | |
| ty::Ref(_, inner_ty, mutbl) if has_iter_method(cx, *inner_ty).is_some() => { | |
| let method_name = match mutbl { | |
| Mutability::Mut => "iter_mut", | |
| Mutability::Not => "iter", | |
| }; | |
| let caller = match &arg.kind { | |
| ExprKind::AddrOf(BorrowKind::Ref, _, arg_inner) => arg_inner, | |
| _ => arg, | |
| }; | |
| format!( | |
| "{}.{method_name}()", | |
| sugg::Sugg::hir_with_applicability(cx, caller, "_", applicability).maybe_paren(), | |
| ) | |
| }, | |
| _ => format!( | |
| "{}.into_iter()", | |
| sugg::Sugg::hir_with_applicability(cx, arg, "_", applicability).maybe_paren() | |
| ), | |
| } | |
| } | |
| } |