diff --git a/clippy_lints/src/bytecount.rs b/clippy_lints/src/bytecount.rs index c444984bc13..a07cd5e5f4e 100644 --- a/clippy_lints/src/bytecount.rs +++ b/clippy_lints/src/bytecount.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::match_type; -use clippy_utils::visitors::LocalUsedVisitor; +use clippy_utils::visitors::is_local_used; use clippy_utils::{path_to_local_id, paths, peel_ref_operators, remove_blocks, strip_pat_refs}; use if_chain::if_chain; use rustc_errors::Applicability; @@ -65,7 +65,7 @@ impl<'tcx> LateLintPass<'tcx> for ByteCount { return; }; if ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(needle).peel_refs().kind(); - if !LocalUsedVisitor::new(cx, arg_id).check_expr(needle); + if !is_local_used(cx, needle, arg_id); then { let haystack = if let ExprKind::MethodCall(path, _, args, _) = filter_recv.kind { diff --git a/clippy_lints/src/collapsible_match.rs b/clippy_lints/src/collapsible_match.rs index a403a9846ba..bd8f9cc7343 100644 --- a/clippy_lints/src/collapsible_match.rs +++ b/clippy_lints/src/collapsible_match.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::visitors::LocalUsedVisitor; +use clippy_utils::visitors::is_local_used; use clippy_utils::{is_lang_ctor, path_to_local, peel_ref_operators, SpanlessEq}; use if_chain::if_chain; use rustc_hir::LangItem::OptionNone; @@ -83,13 +83,12 @@ fn check_arm<'tcx>(arm: &Arm<'tcx>, wild_outer_arm: &Arm<'tcx>, cx: &LateContext // the "wild-like" branches must be equal if SpanlessEq::new(cx).eq_expr(wild_inner_arm.body, wild_outer_arm.body); // the binding must not be used in the if guard - let mut used_visitor = LocalUsedVisitor::new(cx, binding_id); if match arm.guard { None => true, - Some(Guard::If(expr) | Guard::IfLet(_, expr)) => !used_visitor.check_expr(expr), + Some(Guard::If(expr) | Guard::IfLet(_, expr)) => !is_local_used(cx, expr, binding_id), }; // ...or anywhere in the inner match - if !arms_inner.iter().any(|arm| used_visitor.check_arm(arm)); + if !arms_inner.iter().any(|arm| is_local_used(cx, arm, binding_id)); then { span_lint_and_then( cx, diff --git a/clippy_lints/src/let_if_seq.rs b/clippy_lints/src/let_if_seq.rs index 13f0d43cf8d..834440e912d 100644 --- a/clippy_lints/src/let_if_seq.rs +++ b/clippy_lints/src/let_if_seq.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; -use clippy_utils::{path_to_local_id, visitors::LocalUsedVisitor}; +use clippy_utils::{path_to_local_id, visitors::is_local_used}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; @@ -65,11 +65,10 @@ impl<'tcx> LateLintPass<'tcx> for LetIfSeq { if let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind; if let hir::StmtKind::Expr(if_) = expr.kind; if let hir::ExprKind::If(cond, then, ref else_) = if_.kind; - let mut used_visitor = LocalUsedVisitor::new(cx, canonical_id); - if !used_visitor.check_expr(cond); + if !is_local_used(cx, cond, canonical_id); if let hir::ExprKind::Block(then, _) = then.kind; if let Some(value) = check_assign(cx, canonical_id, &*then); - if !used_visitor.check_expr(value); + if !is_local_used(cx, value, canonical_id); then { let span = stmt.span.to(if_.span); @@ -148,15 +147,13 @@ fn check_assign<'tcx>( if let hir::ExprKind::Assign(var, value, _) = expr.kind; if path_to_local_id(var, decl); then { - let mut v = LocalUsedVisitor::new(cx, decl); - - if block.stmts.iter().take(block.stmts.len()-1).any(|stmt| v.check_stmt(stmt)) { - return None; + if block.stmts.iter().take(block.stmts.len()-1).any(|stmt| is_local_used(cx, stmt, decl)) { + None + } else { + Some(value) } - - return Some(value); + } else { + None } } - - None } diff --git a/clippy_lints/src/loops/for_kv_map.rs b/clippy_lints/src/loops/for_kv_map.rs index 82bf49f5b49..68bef2f4c8b 100644 --- a/clippy_lints/src/loops/for_kv_map.rs +++ b/clippy_lints/src/loops/for_kv_map.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; use clippy_utils::source::snippet; use clippy_utils::sugg; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::visitors::LocalUsedVisitor; +use clippy_utils::visitors::is_local_used; use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind}; use rustc_lint::LateContext; use rustc_middle::ty; @@ -66,9 +66,7 @@ pub(super) fn check<'tcx>( fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: &'tcx Expr<'_>) -> bool { match *pat { PatKind::Wild => true, - PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => { - !LocalUsedVisitor::new(cx, id).check_expr(body) - }, + PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => !is_local_used(cx, body, id), _ => false, } } diff --git a/clippy_lints/src/loops/manual_flatten.rs b/clippy_lints/src/loops/manual_flatten.rs index d8153abc965..2af6cfe35b5 100644 --- a/clippy_lints/src/loops/manual_flatten.rs +++ b/clippy_lints/src/loops/manual_flatten.rs @@ -1,7 +1,7 @@ use super::utils::make_iterator_snippet; use super::MANUAL_FLATTEN; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::visitors::LocalUsedVisitor; +use clippy_utils::visitors::is_local_used; use clippy_utils::{is_lang_ctor, path_to_local_id}; use if_chain::if_chain; use rustc_errors::Applicability; @@ -49,7 +49,7 @@ pub(super) fn check<'tcx>( let ok_ctor = is_lang_ctor(cx, qpath, ResultOk); if some_ctor || ok_ctor; // Ensure epxr in `if let` is not used afterwards - if !LocalUsedVisitor::new(cx, pat_hir_id).check_arm(true_arm); + if !is_local_used(cx, true_arm, pat_hir_id); then { let if_let_type = if some_ctor { "Some" } else { "Ok" }; // Prepare the error message diff --git a/clippy_lints/src/loops/needless_range_loop.rs b/clippy_lints/src/loops/needless_range_loop.rs index 3810d0dcc05..fa33d068d3d 100644 --- a/clippy_lints/src/loops/needless_range_loop.rs +++ b/clippy_lints/src/loops/needless_range_loop.rs @@ -2,10 +2,8 @@ use super::NEEDLESS_RANGE_LOOP; use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; use clippy_utils::source::snippet; use clippy_utils::ty::has_iter_method; -use clippy_utils::visitors::LocalUsedVisitor; -use clippy_utils::{ - contains_name, higher, is_integer_const, match_trait_method, path_to_local_id, paths, sugg, SpanlessEq, -}; +use clippy_utils::visitors::is_local_used; +use clippy_utils::{contains_name, higher, is_integer_const, match_trait_method, paths, sugg, SpanlessEq}; use if_chain::if_chain; use rustc_ast::ast; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; @@ -256,43 +254,36 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> { if let ExprKind::Path(ref seqpath) = seqexpr.kind; if let QPath::Resolved(None, seqvar) = *seqpath; if seqvar.segments.len() == 1; - let index_used_directly = path_to_local_id(idx, self.var); - let indexed_indirectly = { - let mut used_visitor = LocalUsedVisitor::new(self.cx, self.var); - walk_expr(&mut used_visitor, idx); - used_visitor.used - }; - if indexed_indirectly || index_used_directly; + if is_local_used(self.cx, idx, self.var); then { if self.prefer_mutable { self.indexed_mut.insert(seqvar.segments[0].ident.name); } + let index_used_directly = matches!(idx.kind, ExprKind::Path(_)); let res = self.cx.qpath_res(seqpath, seqexpr.hir_id); match res { Res::Local(hir_id) => { let parent_id = self.cx.tcx.hir().get_parent_item(expr.hir_id); let parent_def_id = self.cx.tcx.hir().local_def_id(parent_id); let extent = self.cx.tcx.region_scope_tree(parent_def_id).var_scope(hir_id.local_id); - if indexed_indirectly { - self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent)); - } if index_used_directly { self.indexed_directly.insert( seqvar.segments[0].ident.name, (Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)), ); + } else { + self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent)); } return false; // no need to walk further *on the variable* } Res::Def(DefKind::Static | DefKind::Const, ..) => { - if indexed_indirectly { - self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None); - } if index_used_directly { self.indexed_directly.insert( seqvar.segments[0].ident.name, (None, self.cx.typeck_results().node_type(seqexpr.hir_id)), ); + } else { + self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None); } return false; // no need to walk further *on the variable* } diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs index 5360c02f905..149c9bee9fb 100644 --- a/clippy_lints/src/matches.rs +++ b/clippy_lints/src/matches.rs @@ -5,7 +5,7 @@ use clippy_utils::diagnostics::{ use clippy_utils::source::{expr_block, indent_of, snippet, snippet_block, snippet_opt, snippet_with_applicability}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, match_type, peel_mid_ty_refs}; -use clippy_utils::visitors::LocalUsedVisitor; +use clippy_utils::visitors::is_local_used; use clippy_utils::{ get_parent_expr, in_macro, is_expn_of, is_lang_ctor, is_lint_allowed, is_refutable, is_wild, meets_msrv, msrvs, path_to_local, path_to_local_id, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns, remove_blocks, @@ -953,9 +953,7 @@ fn check_wild_err_arm<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm // Looking for unused bindings (i.e.: `_e`) for pat in inner.iter() { if let PatKind::Binding(_, id, ident, None) = pat.kind { - if ident.as_str().starts_with('_') - && !LocalUsedVisitor::new(cx, id).check_expr(arm.body) - { + if ident.as_str().starts_with('_') && !is_local_used(cx, arm.body, id) { ident_bind_name = (&ident.name.as_str()).to_string(); matching_wild = true; } diff --git a/clippy_lints/src/unused_self.rs b/clippy_lints/src/unused_self.rs index 658ac81f6ea..e7e249c79a2 100644 --- a/clippy_lints/src/unused_self.rs +++ b/clippy_lints/src/unused_self.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::visitors::LocalUsedVisitor; +use clippy_utils::visitors::is_local_used; use if_chain::if_chain; use rustc_hir::{Impl, ImplItem, ImplItemKind, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -50,8 +50,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedSelf { if let ImplItemKind::Fn(.., body_id) = &impl_item.kind; let body = cx.tcx.hir().body(*body_id); if let [self_param, ..] = body.params; - let self_hir_id = self_param.pat.hir_id; - if !LocalUsedVisitor::new(cx, self_hir_id).check_body(body); + if !is_local_used(cx, body, self_param.pat.hir_id); then { span_lint_and_help( cx, diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index bd229402f41..603e831459d 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -2,6 +2,7 @@ #![feature(in_band_lifetimes)] #![feature(iter_zip)] #![feature(rustc_private)] +#![feature(control_flow_enum)] #![recursion_limit = "512"] #![cfg_attr(feature = "deny-warnings", deny(warnings))] #![allow(clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate)] diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs index ce00106dd4d..52f95ff34f5 100644 --- a/clippy_utils/src/visitors.rs +++ b/clippy_utils/src/visitors.rs @@ -4,6 +4,7 @@ use rustc_hir::intravisit::{self, walk_expr, ErasedMap, NestedVisitorMap, Visito use rustc_hir::{def::Res, Arm, Block, Body, BodyId, Destination, Expr, ExprKind, HirId, Stmt}; use rustc_lint::LateContext; use rustc_middle::hir::map::Map; +use std::ops::ControlFlow; /// returns `true` if expr contains match expr desugared from try fn contains_try(expr: &hir::Expr<'_>) -> bool { @@ -133,62 +134,6 @@ where } } -pub struct LocalUsedVisitor<'hir> { - hir: Map<'hir>, - pub local_hir_id: HirId, - pub used: bool, -} - -impl<'hir> LocalUsedVisitor<'hir> { - pub fn new(cx: &LateContext<'hir>, local_hir_id: HirId) -> Self { - Self { - hir: cx.tcx.hir(), - local_hir_id, - used: false, - } - } - - fn check(&mut self, t: T, visit: fn(&mut Self, T)) -> bool { - visit(self, t); - std::mem::replace(&mut self.used, false) - } - - pub fn check_arm(&mut self, arm: &'hir Arm<'_>) -> bool { - self.check(arm, Self::visit_arm) - } - - pub fn check_body(&mut self, body: &'hir Body<'_>) -> bool { - self.check(body, Self::visit_body) - } - - pub fn check_expr(&mut self, expr: &'hir Expr<'_>) -> bool { - self.check(expr, Self::visit_expr) - } - - pub fn check_stmt(&mut self, stmt: &'hir Stmt<'_>) -> bool { - self.check(stmt, Self::visit_stmt) - } -} - -impl<'v> Visitor<'v> for LocalUsedVisitor<'v> { - type Map = Map<'v>; - - fn visit_expr(&mut self, expr: &'v Expr<'v>) { - if self.used { - return; - } - if path_to_local_id(expr, self.local_hir_id) { - self.used = true; - } else { - walk_expr(self, expr); - } - } - - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::OnlyBodies(self.hir) - } -} - /// A type which can be visited. pub trait Visitable<'tcx> { /// Calls the corresponding `visit_*` function on the visitor. @@ -202,8 +147,22 @@ macro_rules! visitable_ref { } } }; + ([$t:ident], $f:ident) => { + impl Visitable<'tcx> for &'tcx [$t<'tcx>] { + fn visit>(self, visitor: &mut V) { + for x in self { + visitor.$f(x); + } + } + } + }; } +visitable_ref!(Arm, visit_arm); visitable_ref!(Block, visit_block); +visitable_ref!(Body, visit_body); +visitable_ref!(Expr, visit_expr); +visitable_ref!(Stmt, visit_stmt); +visitable_ref!([Stmt], visit_stmt); /// Calls the given function for each break expression. pub fn visit_break_exprs<'tcx>( @@ -260,3 +219,48 @@ pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool { v.visit_expr(&cx.tcx.hir().body(body).value); v.found } + +/// Calls the given function for each usage of the given local. +pub fn for_each_local_usage<'tcx, B>( + cx: &LateContext<'tcx>, + visitable: impl Visitable<'tcx>, + id: HirId, + f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow, +) -> ControlFlow { + struct V<'tcx, B, F> { + map: Map<'tcx>, + id: HirId, + f: F, + res: ControlFlow, + } + impl<'tcx, B, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow> Visitor<'tcx> for V<'tcx, B, F> { + type Map = Map<'tcx>; + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::OnlyBodies(self.map) + } + + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + if self.res.is_continue() { + if path_to_local_id(e, self.id) { + self.res = (self.f)(e); + } else { + walk_expr(self, e); + } + } + } + } + + let mut v = V { + map: cx.tcx.hir(), + id, + f, + res: ControlFlow::CONTINUE, + }; + visitable.visit(&mut v); + v.res +} + +/// Checks if the given local is used. +pub fn is_local_used(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool { + for_each_local_usage(cx, visitable, id, |_| ControlFlow::BREAK).is_break() +}