Auto merge of #7938 - camsteffen:visitors, r=xFrednet

Introduce `expr_visitor` and `expr_visitor_no_bodies`

changelog: none

A couple utils that satisfy a *lot* of visitor use cases. Factoring in every possible usage would be really big so I just focused on cleaning clippy_utils.
This commit is contained in:
bors 2021-11-08 13:39:58 +00:00
commit 94517d397c
8 changed files with 164 additions and 312 deletions

View File

@ -1,8 +1,8 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::higher::VecArgs; use clippy_utils::higher::VecArgs;
use clippy_utils::source::snippet_opt; use clippy_utils::source::snippet_opt;
use clippy_utils::usage::UsedAfterExprVisitor; use clippy_utils::usage::local_used_after_expr;
use clippy_utils::{get_enclosing_loop_or_closure, higher, path_to_local_id}; use clippy_utils::{get_enclosing_loop_or_closure, higher, path_to_local, path_to_local_id};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
@ -118,7 +118,7 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
if let ty::Closure(_, substs) = callee_ty.peel_refs().kind(); if let ty::Closure(_, substs) = callee_ty.peel_refs().kind();
if substs.as_closure().kind() == ClosureKind::FnMut; if substs.as_closure().kind() == ClosureKind::FnMut;
if get_enclosing_loop_or_closure(cx.tcx, expr).is_some() if get_enclosing_loop_or_closure(cx.tcx, expr).is_some()
|| UsedAfterExprVisitor::is_found(cx, callee); || path_to_local(callee).map_or(false, |l| local_used_after_expr(cx, l, callee));
then { then {
// Mutable closure is used after current expr; we cannot consume it. // Mutable closure is used after current expr; we cannot consume it.

View File

@ -10,7 +10,7 @@ use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::adjustment::{Adjust, Adjustment}; use rustc_middle::ty::adjustment::{Adjust, Adjustment};
use rustc_middle::ty::Ty; use rustc_middle::ty::Ty;
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{sym, BytePos, ExpnData, ExpnKind, Span, Symbol}; use rustc_span::{sym, ExpnData, ExpnKind, Span, Symbol};
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -128,7 +128,7 @@ fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symb
span_lint_and_then( span_lint_and_then(
cx, cx,
FORMAT_IN_FORMAT_ARGS, FORMAT_IN_FORMAT_ARGS,
trim_semicolon(cx, call_site), call_site,
&format!("`format!` in `{}!` args", name), &format!("`format!` in `{}!` args", name),
|diag| { |diag| {
diag.help(&format!( diag.help(&format!(
@ -192,13 +192,6 @@ fn is_aliased(args: &[FormatArgsArg<'_>], i: usize) -> bool {
.any(|(j, arg)| i != j && std::ptr::eq(value, arg.value)) .any(|(j, arg)| i != j && std::ptr::eq(value, arg.value))
} }
fn trim_semicolon(cx: &LateContext<'_>, span: Span) -> Span {
snippet_opt(cx, span).map_or(span, |snippet| {
let snippet = snippet.trim_end_matches(';');
span.with_hi(span.lo() + BytePos(u32::try_from(snippet.len()).unwrap()))
})
}
fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tcx>) fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tcx>)
where where
I: Iterator<Item = &'tcx Adjustment<'tcx>>, I: Iterator<Item = &'tcx Adjustment<'tcx>>,

View File

@ -2,10 +2,10 @@ use clippy_utils::{
diagnostics::span_lint_and_sugg, diagnostics::span_lint_and_sugg,
get_async_fn_body, is_async_fn, get_async_fn_body, is_async_fn,
source::{snippet_with_applicability, snippet_with_context, walk_span_to_context}, source::{snippet_with_applicability, snippet_with_context, walk_span_to_context},
visitors::visit_break_exprs, visitors::expr_visitor_no_bodies,
}; };
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind; use rustc_hir::intravisit::{FnKind, Visitor};
use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, FnRetTy, HirId}; use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, FnRetTy, HirId};
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
@ -144,20 +144,24 @@ fn lint_implicit_returns(
ExprKind::Loop(block, ..) => { ExprKind::Loop(block, ..) => {
let mut add_return = false; let mut add_return = false;
visit_break_exprs(block, |break_expr, dest, sub_expr| { expr_visitor_no_bodies(|e| {
if dest.target_id.ok() == Some(expr.hir_id) { if let ExprKind::Break(dest, sub_expr) = e.kind {
if call_site_span.is_none() && break_expr.span.ctxt() == ctxt { if dest.target_id.ok() == Some(expr.hir_id) {
// At this point sub_expr can be `None` in async functions which either diverge, or return the if call_site_span.is_none() && e.span.ctxt() == ctxt {
// unit type. // At this point sub_expr can be `None` in async functions which either diverge, or return
if let Some(sub_expr) = sub_expr { // the unit type.
lint_break(cx, break_expr.span, sub_expr.span); if let Some(sub_expr) = sub_expr {
lint_break(cx, e.span, sub_expr.span);
}
} else {
// the break expression is from a macro call, add a return to the loop
add_return = true;
} }
} else {
// the break expression is from a macro call, add a return to the loop
add_return = true;
} }
} }
}); true
})
.visit_block(block);
if add_return { if add_return {
#[allow(clippy::option_if_let_else)] #[allow(clippy::option_if_let_else)]
if let Some(span) = call_site_span { if let Some(span) = call_site_span {

View File

@ -632,9 +632,6 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
if let ExprKind::Match(ex, arms, _) = expr.kind { if let ExprKind::Match(ex, arms, _) = expr.kind {
check_match_ref_pats(cx, ex, arms.iter().map(|el| el.pat), expr); check_match_ref_pats(cx, ex, arms.iter().map(|el| el.pat), expr);
} }
if let Some(higher::IfLet { let_pat, let_expr, .. }) = higher::IfLet::hir(cx, expr) {
check_match_ref_pats(cx, let_expr, once(let_pat), expr);
}
} }
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) { fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {

View File

@ -1,6 +1,7 @@
#![feature(box_patterns)] #![feature(box_patterns)]
#![feature(in_band_lifetimes)] #![feature(in_band_lifetimes)]
#![feature(iter_zip)] #![feature(iter_zip)]
#![feature(let_else)]
#![feature(rustc_private)] #![feature(rustc_private)]
#![feature(control_flow_enum)] #![feature(control_flow_enum)]
#![recursion_limit = "512"] #![recursion_limit = "512"]
@ -68,7 +69,7 @@ use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
use rustc_hir::hir_id::{HirIdMap, HirIdSet}; use rustc_hir::hir_id::{HirIdMap, HirIdSet};
use rustc_hir::intravisit::{self, walk_expr, ErasedMap, FnKind, NestedVisitorMap, Visitor}; use rustc_hir::intravisit::{walk_expr, ErasedMap, FnKind, NestedVisitorMap, Visitor};
use rustc_hir::itemlikevisit::ItemLikeVisitor; use rustc_hir::itemlikevisit::ItemLikeVisitor;
use rustc_hir::LangItem::{OptionNone, ResultErr, ResultOk}; use rustc_hir::LangItem::{OptionNone, ResultErr, ResultOk};
use rustc_hir::{ use rustc_hir::{
@ -96,6 +97,7 @@ use rustc_target::abi::Integer;
use crate::consts::{constant, Constant}; use crate::consts::{constant, Constant};
use crate::ty::{can_partially_move_ty, is_copy, is_recursively_primitive_type}; use crate::ty::{can_partially_move_ty, is_copy, is_recursively_primitive_type};
use crate::visitors::expr_visitor_no_bodies;
pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> { pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
if let Ok(version) = RustcVersion::parse(msrv) { if let Ok(version) = RustcVersion::parse(msrv) {
@ -1107,63 +1109,30 @@ pub fn contains_name(name: Symbol, expr: &Expr<'_>) -> bool {
/// Returns `true` if `expr` contains a return expression /// Returns `true` if `expr` contains a return expression
pub fn contains_return(expr: &hir::Expr<'_>) -> bool { pub fn contains_return(expr: &hir::Expr<'_>) -> bool {
struct RetCallFinder { let mut found = false;
found: bool, expr_visitor_no_bodies(|expr| {
} if !found {
impl<'tcx> hir::intravisit::Visitor<'tcx> for RetCallFinder {
type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
if self.found {
return;
}
if let hir::ExprKind::Ret(..) = &expr.kind { if let hir::ExprKind::Ret(..) = &expr.kind {
self.found = true; found = true;
} else {
hir::intravisit::walk_expr(self, expr);
} }
} }
!found
fn nested_visit_map(&mut self) -> hir::intravisit::NestedVisitorMap<Self::Map> { })
hir::intravisit::NestedVisitorMap::None .visit_expr(expr);
} found
}
let mut visitor = RetCallFinder { found: false };
visitor.visit_expr(expr);
visitor.found
}
struct FindMacroCalls<'a, 'b> {
names: &'a [&'b str],
result: Vec<Span>,
}
impl<'a, 'b, 'tcx> Visitor<'tcx> for FindMacroCalls<'a, 'b> {
type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if self.names.iter().any(|fun| is_expn_of(expr.span, fun).is_some()) {
self.result.push(expr.span);
}
// and check sub-expressions
intravisit::walk_expr(self, expr);
}
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
} }
/// Finds calls of the specified macros in a function body. /// Finds calls of the specified macros in a function body.
pub fn find_macro_calls(names: &[&str], body: &Body<'_>) -> Vec<Span> { pub fn find_macro_calls(names: &[&str], body: &Body<'_>) -> Vec<Span> {
let mut fmc = FindMacroCalls { let mut result = Vec::new();
names, expr_visitor_no_bodies(|expr| {
result: Vec::new(), if names.iter().any(|fun| is_expn_of(expr.span, fun).is_some()) {
}; result.push(expr.span);
fmc.visit_expr(&body.value); }
fmc.result true
})
.visit_expr(&body.value);
result
} }
/// Extends the span to the beginning of the spans line, incl. whitespaces. /// Extends the span to the beginning of the spans line, incl. whitespaces.

View File

@ -1,9 +1,9 @@
use crate::source::snippet; use crate::source::snippet;
use crate::visitors::expr_visitor_no_bodies;
use crate::{path_to_local_id, strip_pat_refs}; use crate::{path_to_local_id, strip_pat_refs};
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; use rustc_hir::intravisit::Visitor;
use rustc_hir::{Body, BodyId, Expr, ExprKind, HirId, PatKind}; use rustc_hir::{Body, BodyId, ExprKind, HirId, PatKind};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::hir::map::Map;
use rustc_span::Span; use rustc_span::Span;
use std::borrow::Cow; use std::borrow::Cow;
@ -30,50 +30,28 @@ fn extract_clone_suggestions<'tcx>(
replace: &[(&'static str, &'static str)], replace: &[(&'static str, &'static str)],
body: &'tcx Body<'_>, body: &'tcx Body<'_>,
) -> Option<Vec<(Span, Cow<'static, str>)>> { ) -> Option<Vec<(Span, Cow<'static, str>)>> {
let mut visitor = PtrCloneVisitor { let mut abort = false;
cx, let mut spans = Vec::new();
id, expr_visitor_no_bodies(|expr| {
replace, if abort {
spans: vec![], return false;
abort: false,
};
visitor.visit_body(body);
if visitor.abort { None } else { Some(visitor.spans) }
}
struct PtrCloneVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
id: HirId,
replace: &'a [(&'static str, &'static str)],
spans: Vec<(Span, Cow<'static, str>)>,
abort: bool,
}
impl<'a, 'tcx> Visitor<'tcx> for PtrCloneVisitor<'a, 'tcx> {
type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if self.abort {
return;
} }
if let ExprKind::MethodCall(seg, _, [recv], _) = expr.kind { if let ExprKind::MethodCall(seg, _, [recv], _) = expr.kind {
if path_to_local_id(recv, self.id) { if path_to_local_id(recv, id) {
if seg.ident.name.as_str() == "capacity" { if seg.ident.name.as_str() == "capacity" {
self.abort = true; abort = true;
return; return false;
} }
for &(fn_name, suffix) in self.replace { for &(fn_name, suffix) in replace {
if seg.ident.name.as_str() == fn_name { if seg.ident.name.as_str() == fn_name {
self.spans.push((expr.span, snippet(self.cx, recv.span, "_") + suffix)); spans.push((expr.span, snippet(cx, recv.span, "_") + suffix));
return; return false;
} }
} }
} }
} }
walk_expr(self, expr); !abort
} })
.visit_body(body);
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { if abort { None } else { Some(spans) }
NestedVisitorMap::None
}
} }

View File

@ -1,7 +1,7 @@
use crate as utils; use crate as utils;
use crate::visitors::{expr_visitor, expr_visitor_no_bodies};
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::intravisit; use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
use rustc_hir::HirIdSet; use rustc_hir::HirIdSet;
use rustc_hir::{Expr, ExprKind, HirId}; use rustc_hir::{Expr, ExprKind, HirId};
use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::infer::TyCtxtInferExt;
@ -148,96 +148,47 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for BindingUsageFinder<'a, 'tcx> {
} }
} }
struct ReturnBreakContinueMacroVisitor { pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
seen_return_break_continue: bool, let mut seen_return_break_continue = false;
} expr_visitor_no_bodies(|ex| {
if seen_return_break_continue {
impl ReturnBreakContinueMacroVisitor { return false;
fn new() -> ReturnBreakContinueMacroVisitor {
ReturnBreakContinueMacroVisitor {
seen_return_break_continue: false,
}
}
}
impl<'tcx> Visitor<'tcx> for ReturnBreakContinueMacroVisitor {
type Map = Map<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
if self.seen_return_break_continue {
// No need to look farther if we've already seen one of them
return;
} }
match &ex.kind { match &ex.kind {
ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => { ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => {
self.seen_return_break_continue = true; seen_return_break_continue = true;
}, },
// Something special could be done here to handle while or for loop // Something special could be done here to handle while or for loop
// desugaring, as this will detect a break if there's a while loop // desugaring, as this will detect a break if there's a while loop
// or a for loop inside the expression. // or a for loop inside the expression.
_ => { _ => {
if ex.span.from_expansion() { if ex.span.from_expansion() {
self.seen_return_break_continue = true; seen_return_break_continue = true;
} else {
rustc_hir::intravisit::walk_expr(self, ex);
} }
}, },
} }
} !seen_return_break_continue
})
.visit_expr(expression);
seen_return_break_continue
} }
pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool { pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr<'_>) -> bool {
let mut recursive_visitor = ReturnBreakContinueMacroVisitor::new(); let Some(block) = utils::get_enclosing_block(cx, local_id) else { return false };
recursive_visitor.visit_expr(expression); let mut used_after_expr = false;
recursive_visitor.seen_return_break_continue let mut past_expr = false;
} expr_visitor(cx, |expr| {
if used_after_expr {
pub struct UsedAfterExprVisitor<'a, 'tcx> { return false;
cx: &'a LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
definition: HirId,
past_expr: bool,
used_after_expr: bool,
}
impl<'a, 'tcx> UsedAfterExprVisitor<'a, 'tcx> {
pub fn is_found(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
utils::path_to_local(expr).map_or(false, |definition| {
let mut visitor = UsedAfterExprVisitor {
cx,
expr,
definition,
past_expr: false,
used_after_expr: false,
};
utils::get_enclosing_block(cx, definition).map_or(false, |block| {
visitor.visit_block(block);
visitor.used_after_expr
})
})
}
}
impl<'a, 'tcx> intravisit::Visitor<'tcx> for UsedAfterExprVisitor<'a, 'tcx> {
type Map = Map<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
}
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
if self.used_after_expr {
return;
} }
if expr.hir_id == self.expr.hir_id { if expr.hir_id == after.hir_id {
self.past_expr = true; past_expr = true;
} else if self.past_expr && utils::path_to_local_id(expr, self.definition) { } else if past_expr && utils::path_to_local_id(expr, local_id) {
self.used_after_expr = true; used_after_expr = true;
} else {
intravisit::walk_expr(self, expr);
} }
} !used_after_expr
})
.visit_block(block);
used_after_expr
} }

View File

@ -1,38 +1,66 @@
use crate::path_to_local_id; use crate::path_to_local_id;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::intravisit::{self, walk_expr, ErasedMap, NestedVisitorMap, Visitor}; use rustc_hir::intravisit::{self, walk_expr, NestedVisitorMap, Visitor};
use rustc_hir::{def::Res, Arm, Block, Body, BodyId, Destination, Expr, ExprKind, HirId, Stmt}; use rustc_hir::{def::Res, Arm, Block, Body, BodyId, Expr, ExprKind, HirId, Stmt};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::hir::map::Map; use rustc_middle::hir::map::Map;
use std::ops::ControlFlow;
/// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested
/// bodies (i.e. closures) are visited.
/// If the callback returns `true`, the expr just provided to the callback is walked.
#[must_use]
pub fn expr_visitor<'tcx>(cx: &LateContext<'tcx>, f: impl FnMut(&'tcx Expr<'tcx>) -> bool) -> impl Visitor<'tcx> {
struct V<'tcx, F> {
hir: Map<'tcx>,
f: F,
}
impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> bool> Visitor<'tcx> for V<'tcx, F> {
type Map = Map<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::OnlyBodies(self.hir)
}
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
if (self.f)(expr) {
walk_expr(self, expr);
}
}
}
V { hir: cx.tcx.hir(), f }
}
/// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested
/// bodies (i.e. closures) are not visited.
/// If the callback returns `true`, the expr just provided to the callback is walked.
#[must_use]
pub fn expr_visitor_no_bodies<'tcx>(f: impl FnMut(&'tcx Expr<'tcx>) -> bool) -> impl Visitor<'tcx> {
struct V<F>(F);
impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> bool> Visitor<'tcx> for V<F> {
type Map = intravisit::ErasedMap<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
if (self.0)(e) {
walk_expr(self, e);
}
}
}
V(f)
}
/// returns `true` if expr contains match expr desugared from try /// returns `true` if expr contains match expr desugared from try
fn contains_try(expr: &hir::Expr<'_>) -> bool { fn contains_try(expr: &hir::Expr<'_>) -> bool {
struct TryFinder { let mut found = false;
found: bool, expr_visitor_no_bodies(|e| {
} if !found {
found = matches!(e.kind, hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar));
impl<'hir> intravisit::Visitor<'hir> for TryFinder {
type Map = Map<'hir>;
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
intravisit::NestedVisitorMap::None
} }
!found
fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) { })
if self.found { .visit_expr(expr);
return; found
}
match expr.kind {
hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar) => self.found = true,
_ => intravisit::walk_expr(self, expr),
}
}
}
let mut visitor = TryFinder { found: false };
visitor.visit_expr(expr);
visitor.found
} }
pub fn find_all_ret_expressions<'hir, F>(_cx: &LateContext<'_>, expr: &'hir hir::Expr<'hir>, callback: F) -> bool pub fn find_all_ret_expressions<'hir, F>(_cx: &LateContext<'_>, expr: &'hir hir::Expr<'hir>, callback: F) -> bool
@ -165,103 +193,35 @@ visitable_ref!(Stmt, visit_stmt);
// } // }
// } // }
/// Calls the given function for each break expression.
pub fn visit_break_exprs<'tcx>(
node: impl Visitable<'tcx>,
f: impl FnMut(&'tcx Expr<'tcx>, Destination, Option<&'tcx Expr<'tcx>>),
) {
struct V<F>(F);
impl<'tcx, F: FnMut(&'tcx Expr<'tcx>, Destination, Option<&'tcx Expr<'tcx>>)> Visitor<'tcx> for V<F> {
type Map = ErasedMap<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
if let ExprKind::Break(dest, sub_expr) = e.kind {
self.0(e, dest, sub_expr);
}
walk_expr(self, e);
}
}
node.visit(&mut V(f));
}
/// Checks if the given resolved path is used in the given body. /// Checks if the given resolved path is used in the given body.
pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool { pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool {
struct V<'a, 'tcx> { let mut found = false;
cx: &'a LateContext<'tcx>, expr_visitor(cx, |e| {
res: Res, if found {
found: bool, return false;
}
impl Visitor<'tcx> for V<'_, 'tcx> {
type Map = Map<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
} }
fn visit_expr(&mut self, e: &'tcx Expr<'_>) { if let ExprKind::Path(p) = &e.kind {
if self.found { if cx.qpath_res(p, e.hir_id) == res {
return; found = true;
}
if let ExprKind::Path(p) = &e.kind {
if self.cx.qpath_res(p, e.hir_id) == self.res {
self.found = true;
}
} else {
walk_expr(self, e);
} }
} }
} !found
})
let mut v = V { cx, res, found: false }; .visit_expr(&cx.tcx.hir().body(body).value);
v.visit_expr(&cx.tcx.hir().body(body).value); found
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<B>,
) -> ControlFlow<B> {
struct V<'tcx, B, F> {
map: Map<'tcx>,
id: HirId,
f: F,
res: ControlFlow<B>,
}
impl<'tcx, B, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>> Visitor<'tcx> for V<'tcx, B, F> {
type Map = Map<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
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. /// Checks if the given local is used.
pub fn is_local_used(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool { 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() let mut is_used = false;
let mut visitor = expr_visitor(cx, |expr| {
if !is_used {
is_used = path_to_local_id(expr, id);
}
!is_used
});
visitable.visit(&mut visitor);
drop(visitor);
is_used
} }