Auto merge of #8762 - Jarcho:visitor, r=Jarcho

Replace `expr_visitor` with  `for_each_expr`

This is a minor change which uses `ControlFlow` rather than a boolean. This also runs the visitor rather than returning the visitor, which results in a small readability win as well.

changelog: None
This commit is contained in:
bors 2022-10-03 00:05:05 +00:00
commit 2be6c4ae5b
19 changed files with 435 additions and 617 deletions

View file

@ -3,10 +3,11 @@ use clippy_utils::get_parent_expr;
use clippy_utils::higher;
use clippy_utils::source::snippet_block_with_applicability;
use clippy_utils::ty::implements_trait;
use clippy_utils::visitors::{for_each_expr, Descend};
use core::ops::ControlFlow;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_hir::{BlockCheckMode, Closure, Expr, ExprKind};
use rustc_hir::{BlockCheckMode, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -44,39 +45,6 @@ declare_clippy_lint! {
declare_lint_pass!(BlocksInIfConditions => [BLOCKS_IN_IF_CONDITIONS]);
struct ExVisitor<'a, 'tcx> {
found_block: Option<&'tcx Expr<'tcx>>,
cx: &'a LateContext<'tcx>,
}
impl<'a, 'tcx> Visitor<'tcx> for ExVisitor<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
if let ExprKind::Closure(&Closure { body, .. }) = expr.kind {
// do not lint if the closure is called using an iterator (see #1141)
if_chain! {
if let Some(parent) = get_parent_expr(self.cx, expr);
if let ExprKind::MethodCall(_, self_arg, ..) = &parent.kind;
let caller = self.cx.typeck_results().expr_ty(self_arg);
if let Some(iter_id) = self.cx.tcx.get_diagnostic_item(sym::Iterator);
if implements_trait(self.cx, caller, iter_id, &[]);
then {
return;
}
}
let body = self.cx.tcx.hir().body(body);
let ex = &body.value;
if let ExprKind::Block(block, _) = ex.kind {
if !body.value.span.from_expansion() && !block.stmts.is_empty() {
self.found_block = Some(ex);
return;
}
}
}
walk_expr(self, expr);
}
}
const BRACED_EXPR_MESSAGE: &str = "omit braces around single expression condition";
const COMPLEX_BLOCK_MESSAGE: &str = "in an `if` condition, avoid complex blocks or closures with blocks; \
instead, move the block or closure higher and bind it with a `let`";
@ -144,11 +112,31 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInIfConditions {
}
}
} else {
let mut visitor = ExVisitor { found_block: None, cx };
walk_expr(&mut visitor, cond);
if let Some(block) = visitor.found_block {
span_lint(cx, BLOCKS_IN_IF_CONDITIONS, block.span, COMPLEX_BLOCK_MESSAGE);
}
let _: Option<!> = for_each_expr(cond, |e| {
if let ExprKind::Closure(closure) = e.kind {
// do not lint if the closure is called using an iterator (see #1141)
if_chain! {
if let Some(parent) = get_parent_expr(cx, e);
if let ExprKind::MethodCall(_, self_arg, _, _) = &parent.kind;
let caller = cx.typeck_results().expr_ty(self_arg);
if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
if implements_trait(cx, caller, iter_id, &[]);
then {
return ControlFlow::Continue(Descend::No);
}
}
let body = cx.tcx.hir().body(closure.body);
let ex = &body.value;
if let ExprKind::Block(block, _) = ex.kind {
if !body.value.span.from_expansion() && !block.stmts.is_empty() {
span_lint(cx, BLOCKS_IN_IF_CONDITIONS, ex.span, COMPLEX_BLOCK_MESSAGE);
return ControlFlow::Continue(Descend::No);
}
}
}
ControlFlow::Continue(Descend::Yes)
});
}
}
}

View file

@ -3,10 +3,12 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::LimitStack;
use core::ops::ControlFlow;
use rustc_ast::ast::Attribute;
use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId};
use rustc_hir::intravisit::FnKind;
use rustc_hir::{Body, ExprKind, FnDecl, HirId};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::Span;
@ -61,11 +63,27 @@ impl CognitiveComplexity {
return;
}
let expr = &body.value;
let expr = body.value;
let mut cc = 1u64;
let mut returns = 0u64;
let _: Option<!> = for_each_expr(expr, |e| {
match e.kind {
ExprKind::If(_, _, _) => {
cc += 1;
},
ExprKind::Match(_, arms, _) => {
if arms.len() > 1 {
cc += 1;
}
cc += arms.iter().filter(|arm| arm.guard.is_some()).count() as u64;
},
ExprKind::Ret(_) => returns += 1,
_ => {},
}
ControlFlow::Continue(())
});
let mut helper = CcHelper { cc: 1, returns: 0 };
helper.visit_expr(expr);
let CcHelper { cc, returns } = helper;
let ret_ty = cx.typeck_results().node_type(expr.hir_id);
let ret_adjust = if is_type_diagnostic_item(cx, ret_ty, sym::Result) {
returns
@ -74,13 +92,12 @@ impl CognitiveComplexity {
(returns / 2)
};
let mut rust_cc = cc;
// prevent degenerate cases where unreachable code contains `return` statements
if rust_cc >= ret_adjust {
rust_cc -= ret_adjust;
if cc >= ret_adjust {
cc -= ret_adjust;
}
if rust_cc > self.limit.limit() {
if cc > self.limit.limit() {
let fn_span = match kind {
FnKind::ItemFn(ident, _, _) | FnKind::Method(ident, _) => ident.span,
FnKind::Closure => {
@ -107,7 +124,7 @@ impl CognitiveComplexity {
COGNITIVE_COMPLEXITY,
fn_span,
&format!(
"the function has a cognitive complexity of ({rust_cc}/{})",
"the function has a cognitive complexity of ({cc}/{})",
self.limit.limit()
),
None,
@ -140,27 +157,3 @@ impl<'tcx> LateLintPass<'tcx> for CognitiveComplexity {
self.limit.pop_attrs(cx.sess(), attrs, "cognitive_complexity");
}
}
struct CcHelper {
cc: u64,
returns: u64,
}
impl<'tcx> Visitor<'tcx> for CcHelper {
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
walk_expr(self, e);
match e.kind {
ExprKind::If(_, _, _) => {
self.cc += 1;
},
ExprKind::Match(_, arms, _) => {
if arms.len() > 1 {
self.cc += 1;
}
self.cc += arms.iter().filter(|arm| arm.guard.is_some()).count() as u64;
},
ExprKind::Ret(_) => self.returns += 1,
_ => {},
}
}
}

View file

@ -1,7 +1,7 @@
use rustc_ast::ast::Attribute;
use rustc_errors::Applicability;
use rustc_hir::def_id::{DefIdSet, LocalDefId};
use rustc_hir::{self as hir, def::Res, intravisit, QPath};
use rustc_hir::{self as hir, def::Res, QPath};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::{
lint::in_external_macro,
@ -13,8 +13,11 @@ use clippy_utils::attrs::is_proc_macro;
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::is_must_use_ty;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{match_def_path, return_ty, trait_ref_of_method};
use core::ops::ControlFlow;
use super::{DOUBLE_MUST_USE, MUST_USE_CANDIDATE, MUST_USE_UNIT};
pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
@ -200,63 +203,6 @@ fn is_mutable_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, tys: &m
}
}
struct StaticMutVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
mutates_static: bool,
}
impl<'a, 'tcx> intravisit::Visitor<'tcx> for StaticMutVisitor<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
use hir::ExprKind::{AddrOf, Assign, AssignOp, Call, MethodCall};
if self.mutates_static {
return;
}
match expr.kind {
Call(_, args) => {
let mut tys = DefIdSet::default();
for arg in args {
if self.cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id())
&& is_mutable_ty(
self.cx,
self.cx.tcx.typeck(arg.hir_id.owner.def_id).expr_ty(arg),
arg.span,
&mut tys,
)
&& is_mutated_static(arg)
{
self.mutates_static = true;
return;
}
tys.clear();
}
},
MethodCall(_, receiver, args, _) => {
let mut tys = DefIdSet::default();
for arg in std::iter::once(receiver).chain(args.iter()) {
if self.cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id())
&& is_mutable_ty(
self.cx,
self.cx.tcx.typeck(arg.hir_id.owner.def_id).expr_ty(arg),
arg.span,
&mut tys,
)
&& is_mutated_static(arg)
{
self.mutates_static = true;
return;
}
tys.clear();
}
},
Assign(target, ..) | AssignOp(_, target, _) | AddrOf(_, hir::Mutability::Mut, target) => {
self.mutates_static |= is_mutated_static(target);
},
_ => {},
}
}
}
fn is_mutated_static(e: &hir::Expr<'_>) -> bool {
use hir::ExprKind::{Field, Index, Path};
@ -269,10 +215,53 @@ fn is_mutated_static(e: &hir::Expr<'_>) -> bool {
}
fn mutates_static<'tcx>(cx: &LateContext<'tcx>, body: &'tcx hir::Body<'_>) -> bool {
let mut v = StaticMutVisitor {
cx,
mutates_static: false,
};
intravisit::walk_expr(&mut v, body.value);
v.mutates_static
for_each_expr(body.value, |e| {
use hir::ExprKind::{AddrOf, Assign, AssignOp, Call, MethodCall};
match e.kind {
Call(_, args) => {
let mut tys = DefIdSet::default();
for arg in args {
if cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id())
&& is_mutable_ty(
cx,
cx.tcx.typeck(arg.hir_id.owner.def_id).expr_ty(arg),
arg.span,
&mut tys,
)
&& is_mutated_static(arg)
{
return ControlFlow::Break(());
}
tys.clear();
}
ControlFlow::Continue(())
},
MethodCall(_, receiver, args, _) => {
let mut tys = DefIdSet::default();
for arg in std::iter::once(receiver).chain(args.iter()) {
if cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id())
&& is_mutable_ty(
cx,
cx.tcx.typeck(arg.hir_id.owner.def_id).expr_ty(arg),
arg.span,
&mut tys,
)
&& is_mutated_static(arg)
{
return ControlFlow::Break(());
}
tys.clear();
}
ControlFlow::Continue(())
},
Assign(target, ..) | AssignOp(_, target, _) | AddrOf(_, hir::Mutability::Mut, target)
if is_mutated_static(target) =>
{
ControlFlow::Break(())
},
_ => ControlFlow::Continue(()),
}
})
.is_some()
}

View file

@ -5,8 +5,11 @@ use rustc_span::def_id::LocalDefId;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::ty::type_is_unsafe_function;
use clippy_utils::visitors::for_each_expr_with_closures;
use clippy_utils::{iter_input_pats, path_to_local};
use core::ops::ControlFlow;
use super::NOT_UNSAFE_PTR_ARG_DEREF;
pub(super) fn check_fn<'tcx>(
@ -39,21 +42,34 @@ fn check_raw_ptr<'tcx>(
body: &'tcx hir::Body<'tcx>,
def_id: LocalDefId,
) {
let expr = &body.value;
if unsafety == hir::Unsafety::Normal && cx.access_levels.is_exported(def_id) {
let raw_ptrs = iter_input_pats(decl, body)
.filter_map(|arg| raw_ptr_arg(cx, arg))
.collect::<HirIdSet>();
if !raw_ptrs.is_empty() {
let typeck_results = cx.tcx.typeck_body(body.id());
let mut v = DerefVisitor {
cx,
ptrs: raw_ptrs,
typeck_results,
};
intravisit::walk_expr(&mut v, expr);
let typeck = cx.tcx.typeck_body(body.id());
let _: Option<!> = for_each_expr_with_closures(cx, body.value, |e| {
match e.kind {
hir::ExprKind::Call(f, args) if type_is_unsafe_function(cx, typeck.expr_ty(f)) => {
for arg in args {
check_arg(cx, &raw_ptrs, arg);
}
},
hir::ExprKind::MethodCall(_, recv, args, _) => {
let def_id = typeck.type_dependent_def_id(e.hir_id).unwrap();
if cx.tcx.fn_sig(def_id).skip_binder().unsafety == hir::Unsafety::Unsafe {
check_arg(cx, &raw_ptrs, recv);
for arg in args {
check_arg(cx, &raw_ptrs, arg);
}
}
},
hir::ExprKind::Unary(hir::UnOp::Deref, ptr) => check_arg(cx, &raw_ptrs, ptr),
_ => (),
}
ControlFlow::Continue(())
});
}
}
}
@ -70,54 +86,13 @@ fn raw_ptr_arg(cx: &LateContext<'_>, arg: &hir::Param<'_>) -> Option<hir::HirId>
}
}
struct DerefVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
ptrs: HirIdSet,
typeck_results: &'a ty::TypeckResults<'tcx>,
}
impl<'a, 'tcx> intravisit::Visitor<'tcx> for DerefVisitor<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
match expr.kind {
hir::ExprKind::Call(f, args) => {
let ty = self.typeck_results.expr_ty(f);
if type_is_unsafe_function(self.cx, ty) {
for arg in args {
self.check_arg(arg);
}
}
},
hir::ExprKind::MethodCall(_, receiver, args, _) => {
let def_id = self.typeck_results.type_dependent_def_id(expr.hir_id).unwrap();
let base_type = self.cx.tcx.type_of(def_id);
if type_is_unsafe_function(self.cx, base_type) {
self.check_arg(receiver);
for arg in args {
self.check_arg(arg);
}
}
},
hir::ExprKind::Unary(hir::UnOp::Deref, ptr) => self.check_arg(ptr),
_ => (),
}
intravisit::walk_expr(self, expr);
}
}
impl<'a, 'tcx> DerefVisitor<'a, 'tcx> {
fn check_arg(&self, ptr: &hir::Expr<'_>) {
if let Some(id) = path_to_local(ptr) {
if self.ptrs.contains(&id) {
span_lint(
self.cx,
NOT_UNSAFE_PTR_ARG_DEREF,
ptr.span,
"this public function might dereference a raw pointer but is not marked `unsafe`",
);
}
}
fn check_arg(cx: &LateContext<'_>, raw_ptrs: &HirIdSet, arg: &hir::Expr<'_>) {
if path_to_local(arg).map_or(false, |id| raw_ptrs.contains(&id)) {
span_lint(
cx,
NOT_UNSAFE_PTR_ARG_DEREF,
arg.span,
"this public function might dereference a raw pointer but is not marked `unsafe`",
);
}
}

View file

@ -2,10 +2,11 @@ use clippy_utils::{
diagnostics::span_lint_hir_and_then,
get_async_fn_body, is_async_fn,
source::{snippet_with_applicability, snippet_with_context, walk_span_to_context},
visitors::expr_visitor_no_bodies,
visitors::for_each_expr,
};
use core::ops::ControlFlow;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{FnKind, Visitor};
use rustc_hir::intravisit::FnKind;
use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, FnRetTy, HirId};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
@ -152,7 +153,7 @@ fn lint_implicit_returns(
ExprKind::Loop(block, ..) => {
let mut add_return = false;
expr_visitor_no_bodies(|e| {
let _: Option<!> = for_each_expr(block, |e| {
if let ExprKind::Break(dest, sub_expr) = e.kind {
if dest.target_id.ok() == Some(expr.hir_id) {
if call_site_span.is_none() && e.span.ctxt() == ctxt {
@ -167,9 +168,8 @@ fn lint_implicit_returns(
}
}
}
true
})
.visit_block(block);
ControlFlow::Continue(())
});
if add_return {
#[expect(clippy::option_if_let_else)]
if let Some(span) = call_site_span {

View file

@ -2,11 +2,11 @@ use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::snippet_with_context;
use clippy_utils::usage::local_used_after_expr;
use clippy_utils::visitors::expr_visitor;
use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
use clippy_utils::{is_diag_item_method, match_def_path, meets_msrv, msrvs, path_to_local_id, paths};
use core::ops::ControlFlow;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::intravisit::Visitor;
use rustc_hir::{
BindingAnnotation, Expr, ExprKind, HirId, LangItem, Local, MatchSource, Node, Pat, PatKind, QPath, Stmt, StmtKind,
};
@ -211,7 +211,7 @@ fn indirect_usage<'tcx>(
binding: HirId,
ctxt: SyntaxContext,
) -> Option<IndirectUsage<'tcx>> {
if let StmtKind::Local(Local {
if let StmtKind::Local(&Local {
pat: Pat {
kind: PatKind::Binding(BindingAnnotation::NONE, _, ident, None),
..
@ -222,14 +222,12 @@ fn indirect_usage<'tcx>(
}) = stmt.kind
{
let mut path_to_binding = None;
expr_visitor(cx, |expr| {
if path_to_local_id(expr, binding) {
path_to_binding = Some(expr);
let _: Option<!> = for_each_expr_with_closures(cx, init_expr, |e| {
if path_to_local_id(e, binding) {
path_to_binding = Some(e);
}
path_to_binding.is_none()
})
.visit_expr(init_expr);
ControlFlow::Continue(Descend::from(path_to_binding.is_none()))
});
let mut parents = cx.tcx.hir().parent_iter(path_to_binding?.hir_id);
let iter_usage = parse_iter_usage(cx, ctxt, &mut parents)?;
@ -250,7 +248,7 @@ fn indirect_usage<'tcx>(
..
} = iter_usage
{
if parent_id == *local_hir_id {
if parent_id == local_hir_id {
return Some(IndirectUsage {
name: ident.name,
span: stmt.span,

View file

@ -2,9 +2,10 @@ use super::utils::clone_or_copy_needed;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::ty::is_copy;
use clippy_utils::usage::mutated_variables;
use clippy_utils::visitors::{for_each_expr, Descend};
use clippy_utils::{is_res_lang_ctor, is_trait_method, path_res, path_to_local_id};
use core::ops::ControlFlow;
use rustc_hir as hir;
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_lint::LateContext;
use rustc_middle::ty;
@ -13,7 +14,7 @@ use rustc_span::sym;
use super::UNNECESSARY_FILTER_MAP;
use super::UNNECESSARY_FIND_MAP;
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>, name: &str) {
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, arg: &'tcx hir::Expr<'tcx>, name: &str) {
if !is_trait_method(cx, expr, sym::Iterator) {
return;
}
@ -26,10 +27,16 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<
let (mut found_mapping, mut found_filtering) = check_expression(cx, arg_id, body.value);
let mut return_visitor = ReturnVisitor::new(cx, arg_id);
return_visitor.visit_expr(body.value);
found_mapping |= return_visitor.found_mapping;
found_filtering |= return_visitor.found_filtering;
let _: Option<!> = for_each_expr(body.value, |e| {
if let hir::ExprKind::Ret(Some(e)) = &e.kind {
let (found_mapping_res, found_filtering_res) = check_expression(cx, arg_id, e);
found_mapping |= found_mapping_res;
found_filtering |= found_filtering_res;
ControlFlow::Continue(Descend::No)
} else {
ControlFlow::Continue(Descend::Yes)
}
});
let in_ty = cx.typeck_results().node_type(body.params[0].hir_id);
let sugg = if !found_filtering {
@ -97,35 +104,3 @@ fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tc
_ => (true, true),
}
}
struct ReturnVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
arg_id: hir::HirId,
// Found a non-None return that isn't Some(input)
found_mapping: bool,
// Found a return that isn't Some
found_filtering: bool,
}
impl<'a, 'tcx> ReturnVisitor<'a, 'tcx> {
fn new(cx: &'a LateContext<'tcx>, arg_id: hir::HirId) -> ReturnVisitor<'a, 'tcx> {
ReturnVisitor {
cx,
arg_id,
found_mapping: false,
found_filtering: false,
}
}
}
impl<'a, 'tcx> Visitor<'tcx> for ReturnVisitor<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
if let hir::ExprKind::Ret(Some(expr)) = &expr.kind {
let (found_mapping, found_filtering) = check_expression(self.cx, self.arg_id, expr);
self.found_mapping |= found_mapping;
self.found_filtering |= found_filtering;
} else {
walk_expr(self, expr);
}
}
}

View file

@ -2,9 +2,9 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::path_to_local;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::needs_ordered_drop;
use clippy_utils::visitors::{expr_visitor, expr_visitor_no_bodies, is_local_used};
use clippy_utils::visitors::{for_each_expr, for_each_expr_with_closures, is_local_used};
use core::ops::ControlFlow;
use rustc_errors::{Applicability, MultiSpan};
use rustc_hir::intravisit::Visitor;
use rustc_hir::{
BindingAnnotation, Block, Expr, ExprKind, HirId, Local, LocalSource, MatchSource, Node, Pat, PatKind, Stmt,
StmtKind,
@ -64,31 +64,25 @@ declare_clippy_lint! {
declare_lint_pass!(NeedlessLateInit => [NEEDLESS_LATE_INIT]);
fn contains_assign_expr<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) -> bool {
let mut seen = false;
expr_visitor(cx, |expr| {
if let ExprKind::Assign(..) = expr.kind {
seen = true;
for_each_expr_with_closures(cx, stmt, |e| {
if matches!(e.kind, ExprKind::Assign(..)) {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
!seen
})
.visit_stmt(stmt);
seen
.is_some()
}
fn contains_let(cond: &Expr<'_>) -> bool {
let mut seen = false;
expr_visitor_no_bodies(|expr| {
if let ExprKind::Let(_) = expr.kind {
seen = true;
for_each_expr(cond, |e| {
if matches!(e.kind, ExprKind::Let(_)) {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
!seen
})
.visit_expr(cond);
seen
.is_some()
}
fn stmt_needs_ordered_drop(cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool {

View file

@ -2,11 +2,12 @@ use clippy_utils::binop_traits;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::implements_trait;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{eq_expr_value, trait_ref_of_method};
use core::ops::ControlFlow;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_hir_analysis::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
use rustc_lint::LateContext;
use rustc_middle::mir::FakeReadCause;
@ -65,15 +66,19 @@ pub(super) fn check<'tcx>(
}
};
let mut visitor = ExprVisitor {
assignee,
counter: 0,
cx,
};
let mut found = false;
let found_multiple = for_each_expr(e, |e| {
if eq_expr_value(cx, assignee, e) {
if found {
return ControlFlow::Break(());
}
found = true;
}
ControlFlow::Continue(())
})
.is_some();
walk_expr(&mut visitor, e);
if visitor.counter == 1 {
if found && !found_multiple {
// a = a op b
if eq_expr_value(cx, assignee, l) {
lint(assignee, r);
@ -98,22 +103,6 @@ pub(super) fn check<'tcx>(
}
}
struct ExprVisitor<'a, 'tcx> {
assignee: &'a hir::Expr<'a>,
counter: u8,
cx: &'a LateContext<'tcx>,
}
impl<'a, 'tcx> Visitor<'tcx> for ExprVisitor<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
if eq_expr_value(self.cx, self.assignee, expr) {
self.counter += 1;
}
walk_expr(self, expr);
}
}
fn imm_borrows_in_expr(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> hir::HirIdSet {
struct S(hir::HirIdSet);
impl Delegate<'_> for S {

View file

@ -2,9 +2,10 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::root_macro_call_first_node;
use clippy_utils::return_ty;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::expr_visitor_no_bodies;
use clippy_utils::visitors::{for_each_expr, Descend};
use core::ops::ControlFlow;
use rustc_hir as hir;
use rustc_hir::intravisit::{FnKind, Visitor};
use rustc_hir::intravisit::FnKind;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{sym, Span};
@ -58,18 +59,20 @@ impl<'tcx> LateLintPass<'tcx> for PanicInResultFn {
fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, body: &'tcx hir::Body<'tcx>) {
let mut panics = Vec::new();
expr_visitor_no_bodies(|expr| {
let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return true };
let _: Option<!> = for_each_expr(body.value, |e| {
let Some(macro_call) = root_macro_call_first_node(cx, e) else {
return ControlFlow::Continue(Descend::Yes);
};
if matches!(
cx.tcx.item_name(macro_call.def_id).as_str(),
"unimplemented" | "unreachable" | "panic" | "todo" | "assert" | "assert_eq" | "assert_ne"
) {
panics.push(macro_call.span);
return false;
ControlFlow::Continue(Descend::No)
} else {
ControlFlow::Continue(Descend::Yes)
}
true
})
.visit_expr(body.value);
});
if !panics.is_empty() {
span_lint_and_then(
cx,

View file

@ -2,9 +2,10 @@ use clippy_utils::{
diagnostics::{span_lint, span_lint_and_sugg},
higher::{get_vec_init_kind, VecInitKind},
source::snippet,
visitors::expr_visitor_no_bodies,
visitors::for_each_expr,
};
use hir::{intravisit::Visitor, ExprKind, Local, PatKind, PathSegment, QPath, StmtKind};
use core::ops::ControlFlow;
use hir::{Expr, ExprKind, Local, PatKind, PathSegment, QPath, StmtKind};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
@ -58,10 +59,8 @@ impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec {
&& let PatKind::Binding(_, _, ident, _) = pat.kind
&& let Some(vec_init_kind) = get_vec_init_kind(cx, init)
{
// finds use of `_.read(&mut v)`
let mut read_found = false;
let mut visitor = expr_visitor_no_bodies(|expr| {
if let ExprKind::MethodCall(path, _self, [arg], _) = expr.kind
let visitor = |expr: &Expr<'_>| {
if let ExprKind::MethodCall(path, _, [arg], _) = expr.kind
&& let PathSegment { ident: read_or_read_exact, .. } = *path
&& matches!(read_or_read_exact.as_str(), "read" | "read_exact")
&& let ExprKind::AddrOf(_, hir::Mutability::Mut, inner) = arg.kind
@ -69,27 +68,22 @@ impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec {
&& let [inner_seg] = inner_path.segments
&& ident.name == inner_seg.ident.name
{
read_found = true;
}
!read_found
});
let next_stmt_span;
if idx == block.stmts.len() - 1 {
// case { .. stmt; expr }
if let Some(e) = block.expr {
visitor.visit_expr(e);
next_stmt_span = e.span;
ControlFlow::Break(())
} else {
return;
ControlFlow::Continue(())
}
} else {
};
let (read_found, next_stmt_span) =
if let Some(next_stmt) = block.stmts.get(idx + 1) {
// case { .. stmt; stmt; .. }
let next_stmt = &block.stmts[idx + 1];
visitor.visit_stmt(next_stmt);
next_stmt_span = next_stmt.span;
}
drop(visitor);
(for_each_expr(next_stmt, visitor).is_some(), next_stmt.span)
} else if let Some(e) = block.expr {
// case { .. stmt; expr }
(for_each_expr(e, visitor).is_some(), e.span)
} else {
return
};
if read_found && !next_stmt_span.from_expansion() {
let applicability = Applicability::MaybeIncorrect;

View file

@ -1,9 +1,11 @@
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
use clippy_utils::source::{snippet_opt, snippet_with_context};
use clippy_utils::visitors::{for_each_expr, Descend};
use clippy_utils::{fn_def_id, path_to_local_id};
use core::ops::ControlFlow;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
use rustc_hir::intravisit::FnKind;
use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, HirId, MatchSource, PatKind, StmtKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
@ -270,33 +272,20 @@ fn emit_return_lint(
}
fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
let mut visitor = BorrowVisitor { cx, borrows: false };
walk_expr(&mut visitor, expr);
visitor.borrows
}
struct BorrowVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
borrows: bool,
}
impl<'tcx> Visitor<'tcx> for BorrowVisitor<'_, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if self.borrows || expr.span.from_expansion() {
return;
}
if let Some(def_id) = fn_def_id(self.cx, expr) {
self.borrows = self
.cx
for_each_expr(expr, |e| {
if let Some(def_id) = fn_def_id(cx, e)
&& cx
.tcx
.fn_sig(def_id)
.output()
.skip_binder()
.output()
.walk()
.any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_)));
.any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_)))
{
ControlFlow::Break(())
} else {
ControlFlow::Continue(Descend::from(!expr.span.from_expansion()))
}
walk_expr(self, expr);
}
})
.is_some()
}

View file

@ -1,8 +1,9 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{binop_traits, trait_ref_of_method, BINOP_TRAITS, OP_ASSIGN_TRAITS};
use core::ops::ControlFlow;
use if_chain::if_chain;
use rustc_hir as hir;
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -92,25 +93,17 @@ impl<'tcx> LateLintPass<'tcx> for SuspiciousImpl {
}
fn count_binops(expr: &hir::Expr<'_>) -> u32 {
let mut visitor = BinaryExprVisitor::default();
visitor.visit_expr(expr);
visitor.nb_binops
}
#[derive(Default)]
struct BinaryExprVisitor {
nb_binops: u32,
}
impl<'tcx> Visitor<'tcx> for BinaryExprVisitor {
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
match expr.kind {
let mut count = 0u32;
let _: Option<!> = for_each_expr(expr, |e| {
if matches!(
e.kind,
hir::ExprKind::Binary(..)
| hir::ExprKind::Unary(hir::UnOp::Not | hir::UnOp::Neg, _)
| hir::ExprKind::AssignOp(..) => self.nb_binops += 1,
_ => {},
| hir::ExprKind::Unary(hir::UnOp::Not | hir::UnOp::Neg, _)
| hir::ExprKind::AssignOp(..)
) {
count += 1;
}
walk_expr(self, expr);
}
ControlFlow::Continue(())
});
count
}

View file

@ -1,12 +1,12 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{method_chain_args, return_ty};
use core::ops::ControlFlow;
use if_chain::if_chain;
use rustc_hir as hir;
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{Expr, ImplItemKind};
use rustc_hir::ImplItemKind;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{sym, Span};
@ -73,51 +73,37 @@ impl<'tcx> LateLintPass<'tcx> for UnwrapInResult {
}
}
struct FindExpectUnwrap<'a, 'tcx> {
lcx: &'a LateContext<'tcx>,
typeck_results: &'tcx ty::TypeckResults<'tcx>,
result: Vec<Span>,
}
impl<'a, 'tcx> Visitor<'tcx> for FindExpectUnwrap<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
// check for `expect`
if let Some(arglists) = method_chain_args(expr, &["expect"]) {
let receiver_ty = self.typeck_results.expr_ty(arglists[0].0).peel_refs();
if is_type_diagnostic_item(self.lcx, receiver_ty, sym::Option)
|| is_type_diagnostic_item(self.lcx, receiver_ty, sym::Result)
{
self.result.push(expr.span);
}
}
// check for `unwrap`
if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
let receiver_ty = self.typeck_results.expr_ty(arglists[0].0).peel_refs();
if is_type_diagnostic_item(self.lcx, receiver_ty, sym::Option)
|| is_type_diagnostic_item(self.lcx, receiver_ty, sym::Result)
{
self.result.push(expr.span);
}
}
// and check sub-expressions
intravisit::walk_expr(self, expr);
}
}
fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_item: &'tcx hir::ImplItem<'_>) {
if let ImplItemKind::Fn(_, body_id) = impl_item.kind {
let body = cx.tcx.hir().body(body_id);
let mut fpu = FindExpectUnwrap {
lcx: cx,
typeck_results: cx.tcx.typeck(impl_item.def_id.def_id),
result: Vec::new(),
};
fpu.visit_expr(body.value);
let typeck = cx.tcx.typeck(impl_item.def_id.def_id);
let mut result = Vec::new();
let _: Option<!> = for_each_expr(body.value, |e| {
// check for `expect`
if let Some(arglists) = method_chain_args(e, &["expect"]) {
let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs();
if is_type_diagnostic_item(cx, receiver_ty, sym::Option)
|| is_type_diagnostic_item(cx, receiver_ty, sym::Result)
{
result.push(e.span);
}
}
// check for `unwrap`
if let Some(arglists) = method_chain_args(e, &["unwrap"]) {
let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs();
if is_type_diagnostic_item(cx, receiver_ty, sym::Option)
|| is_type_diagnostic_item(cx, receiver_ty, sym::Result)
{
result.push(e.span);
}
}
ControlFlow::Continue(())
});
// if we've found one, lint
if !fpu.result.is_empty() {
if !result.is_empty() {
span_lint_and_then(
cx,
UNWRAP_IN_RESULT,
@ -125,7 +111,7 @@ fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_item: &'tc
"used unwrap or expect in a function that returns result or option",
move |diag| {
diag.help("unwrap and expect should not be used in a function that returns result or option");
diag.span_note(fpu.result, "potential non-recoverable error(s)");
diag.span_note(result, "potential non-recoverable error(s)");
},
);
}

View file

@ -3,6 +3,7 @@
#![feature(control_flow_enum)]
#![feature(let_chains)]
#![feature(lint_reasons)]
#![feature(never_type)]
#![feature(once_cell)]
#![feature(rustc_private)]
#![recursion_limit = "512"]
@ -65,6 +66,7 @@ pub use self::hir_utils::{
both, count_eq, eq_expr_value, hash_expr, hash_stmt, over, HirEqInterExpr, SpanlessEq, SpanlessHash,
};
use core::ops::ControlFlow;
use std::collections::hash_map::Entry;
use std::hash::BuildHasherDefault;
use std::sync::OnceLock;
@ -113,7 +115,7 @@ use rustc_target::abi::Integer;
use crate::consts::{constant, Constant};
use crate::ty::{can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type, ty_is_fn_once_param};
use crate::visitors::expr_visitor_no_bodies;
use crate::visitors::for_each_expr;
pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
if let Ok(version) = RustcVersion::parse(msrv) {
@ -1193,17 +1195,14 @@ pub fn contains_name(name: Symbol, expr: &Expr<'_>) -> bool {
/// Returns `true` if `expr` contains a return expression
pub fn contains_return(expr: &hir::Expr<'_>) -> bool {
let mut found = false;
expr_visitor_no_bodies(|expr| {
if !found {
if let hir::ExprKind::Ret(..) = &expr.kind {
found = true;
}
for_each_expr(expr, |e| {
if matches!(e.kind, hir::ExprKind::Ret(..)) {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
!found
})
.visit_expr(expr);
found
.is_some()
}
/// Extends the span to the beginning of the spans line, incl. whitespaces.

View file

@ -2,7 +2,7 @@
use crate::is_path_diagnostic_item;
use crate::source::snippet_opt;
use crate::visitors::expr_visitor_no_bodies;
use crate::visitors::{for_each_expr, Descend};
use arrayvec::ArrayVec;
use itertools::{izip, Either, Itertools};
@ -270,20 +270,19 @@ fn find_assert_args_inner<'a, const N: usize>(
};
let mut args = ArrayVec::new();
let mut panic_expn = None;
expr_visitor_no_bodies(|e| {
let _: Option<!> = for_each_expr(expr, |e| {
if args.is_full() {
if panic_expn.is_none() && e.span.ctxt() != expr.span.ctxt() {
panic_expn = PanicExpn::parse(cx, e);
}
panic_expn.is_none()
ControlFlow::Continue(Descend::from(panic_expn.is_none()))
} else if is_assert_arg(cx, e, expn) {
args.push(e);
false
ControlFlow::Continue(Descend::No)
} else {
true
ControlFlow::Continue(Descend::Yes)
}
})
.visit_expr(expr);
});
let args = args.into_inner().ok()?;
// if no `panic!(..)` is found, use `PanicExpn::Empty`
// to indicate that the default assertion message is used
@ -297,22 +296,19 @@ fn find_assert_within_debug_assert<'a>(
expn: ExpnId,
assert_name: Symbol,
) -> Option<(&'a Expr<'a>, ExpnId)> {
let mut found = None;
expr_visitor_no_bodies(|e| {
if found.is_some() || !e.span.from_expansion() {
return false;
for_each_expr(expr, |e| {
if !e.span.from_expansion() {
return ControlFlow::Continue(Descend::No);
}
let e_expn = e.span.ctxt().outer_expn();
if e_expn == expn {
return true;
ControlFlow::Continue(Descend::Yes)
} else if e_expn.expn_data().macro_def_id.map(|id| cx.tcx.item_name(id)) == Some(assert_name) {
ControlFlow::Break((e, e_expn))
} else {
ControlFlow::Continue(Descend::No)
}
if e_expn.expn_data().macro_def_id.map(|id| cx.tcx.item_name(id)) == Some(assert_name) {
found = Some((e, e_expn));
}
false
})
.visit_expr(expr);
found
}
fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) -> bool {
@ -396,16 +392,14 @@ impl FormatString {
});
let mut parts = Vec::new();
expr_visitor_no_bodies(|expr| {
if let ExprKind::Lit(lit) = &expr.kind {
if let LitKind::Str(symbol, _) = lit.node {
parts.push(symbol);
}
let _: Option<!> = for_each_expr(pieces, |expr| {
if let ExprKind::Lit(lit) = &expr.kind
&& let LitKind::Str(symbol, _) = lit.node
{
parts.push(symbol);
}
true
})
.visit_expr(pieces);
ControlFlow::Continue(())
});
Some(Self {
span,
@ -431,7 +425,7 @@ impl<'tcx> FormatArgsValues<'tcx> {
fn new(args: &'tcx Expr<'tcx>, format_string_span: SpanData) -> Self {
let mut pos_to_value_index = Vec::new();
let mut value_args = Vec::new();
expr_visitor_no_bodies(|expr| {
let _: Option<!> = for_each_expr(args, |expr| {
if expr.span.ctxt() == args.span.ctxt() {
// ArgumentV1::new_<format_trait>(<val>)
// ArgumentV1::from_usize(<val>)
@ -453,16 +447,13 @@ impl<'tcx> FormatArgsValues<'tcx> {
pos_to_value_index.push(val_idx);
}
true
ControlFlow::Continue(Descend::Yes)
} else {
// assume that any expr with a differing span is a value
value_args.push(expr);
false
ControlFlow::Continue(Descend::No)
}
})
.visit_expr(args);
});
Self {
value_args,
@ -866,22 +857,20 @@ impl<'tcx> FormatArgsExpn<'tcx> {
}
pub fn find_nested(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expn_id: ExpnId) -> Option<Self> {
let mut format_args = None;
expr_visitor_no_bodies(|e| {
if format_args.is_some() {
return false;
}
for_each_expr(expr, |e| {
let e_ctxt = e.span.ctxt();
if e_ctxt == expr.span.ctxt() {
return true;
ControlFlow::Continue(Descend::Yes)
} else if e_ctxt.outer_expn().is_descendant_of(expn_id) {
if let Some(args) = FormatArgsExpn::parse(cx, e) {
ControlFlow::Break(args)
} else {
ControlFlow::Continue(Descend::No)
}
} else {
ControlFlow::Continue(Descend::No)
}
if e_ctxt.outer_expn().is_descendant_of(expn_id) {
format_args = FormatArgsExpn::parse(cx, e);
}
false
})
.visit_expr(expr);
format_args
}
/// Source callsite span of all inputs

View file

@ -1,7 +1,7 @@
use crate::source::snippet;
use crate::visitors::expr_visitor_no_bodies;
use crate::visitors::{for_each_expr, Descend};
use crate::{path_to_local_id, strip_pat_refs};
use rustc_hir::intravisit::Visitor;
use core::ops::ControlFlow;
use rustc_hir::{Body, BodyId, ExprKind, HirId, PatKind};
use rustc_lint::LateContext;
use rustc_span::Span;
@ -30,28 +30,23 @@ fn extract_clone_suggestions<'tcx>(
replace: &[(&'static str, &'static str)],
body: &'tcx Body<'_>,
) -> Option<Vec<(Span, Cow<'static, str>)>> {
let mut abort = false;
let mut spans = Vec::new();
expr_visitor_no_bodies(|expr| {
if abort {
return false;
}
if let ExprKind::MethodCall(seg, recv, [], _) = expr.kind {
if path_to_local_id(recv, id) {
if seg.ident.name.as_str() == "capacity" {
abort = true;
return false;
}
for &(fn_name, suffix) in replace {
if seg.ident.name.as_str() == fn_name {
spans.push((expr.span, snippet(cx, recv.span, "_") + suffix));
return false;
}
for_each_expr(body, |e| {
if let ExprKind::MethodCall(seg, recv, [], _) = e.kind
&& path_to_local_id(recv, id)
{
if seg.ident.as_str() == "capacity" {
return ControlFlow::Break(());
}
for &(fn_name, suffix) in replace {
if seg.ident.as_str() == fn_name {
spans.push((e.span, snippet(cx, recv.span, "_") + suffix));
return ControlFlow::Continue(Descend::No);
}
}
}
!abort
ControlFlow::Continue(Descend::Yes)
})
.visit_body(body);
if abort { None } else { Some(spans) }
.is_none()
.then_some(spans)
}

View file

@ -1,5 +1,6 @@
use crate as utils;
use crate::visitors::{expr_visitor, expr_visitor_no_bodies};
use crate::visitors::{for_each_expr, for_each_expr_with_closures, Descend};
use core::ops::ControlFlow;
use rustc_hir as hir;
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::HirIdSet;
@ -148,28 +149,17 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for BindingUsageFinder<'a, 'tcx> {
}
pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
let mut seen_return_break_continue = false;
expr_visitor_no_bodies(|ex| {
if seen_return_break_continue {
return false;
}
match &ex.kind {
ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => {
seen_return_break_continue = true;
},
for_each_expr(expression, |e| {
match e.kind {
ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => ControlFlow::Break(()),
// 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
// or a for loop inside the expression.
_ => {
if ex.span.from_expansion() {
seen_return_break_continue = true;
}
},
_ if e.span.from_expansion() => ControlFlow::Break(()),
_ => ControlFlow::Continue(()),
}
!seen_return_break_continue
})
.visit_expr(expression);
seen_return_break_continue
.is_some()
}
pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr<'_>) -> bool {
@ -200,23 +190,16 @@ pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr
return true;
}
let mut used_after_expr = false;
let mut past_expr = false;
expr_visitor(cx, |expr| {
if used_after_expr {
return false;
}
if expr.hir_id == after.hir_id {
for_each_expr_with_closures(cx, block, |e| {
if e.hir_id == after.hir_id {
past_expr = true;
return false;
ControlFlow::Continue(Descend::No)
} else if past_expr && utils::path_to_local_id(e, local_id) {
ControlFlow::Break(())
} else {
ControlFlow::Continue(Descend::Yes)
}
if past_expr && utils::path_to_local_id(expr, local_id) {
used_after_expr = true;
}
!used_after_expr
})
.visit_block(block);
used_after_expr
.is_some()
}

View file

@ -5,14 +5,13 @@ use rustc_hir as hir;
use rustc_hir::def::{CtorKind, DefKind, Res};
use rustc_hir::intravisit::{self, walk_block, walk_expr, Visitor};
use rustc_hir::{
Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Let, Pat, QPath, Stmt, UnOp,
UnsafeSource, Unsafety,
AnonConst, Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Let, Pat, QPath,
Stmt, UnOp, UnsafeSource, Unsafety,
};
use rustc_lint::LateContext;
use rustc_middle::hir::map::Map;
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::adjustment::Adjust;
use rustc_middle::ty::{self, Ty, TypeckResults};
use rustc_middle::ty::{self, Ty, TyCtxt, TypeckResults};
use rustc_span::Span;
mod internal {
@ -48,6 +47,26 @@ impl Continue for Descend {
}
}
/// A type which can be visited.
pub trait Visitable<'tcx> {
/// Calls the corresponding `visit_*` function on the visitor.
fn visit<V: Visitor<'tcx>>(self, visitor: &mut V);
}
macro_rules! visitable_ref {
($t:ident, $f:ident) => {
impl<'tcx> Visitable<'tcx> for &'tcx $t<'tcx> {
fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
visitor.$f(self);
}
}
};
}
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);
/// Calls the given function once for each expression contained. This does not enter any bodies or
/// nested items.
pub fn for_each_expr<'tcx, B, C: Continue>(
@ -82,57 +101,63 @@ pub fn for_each_expr<'tcx, B, C: Continue>(
v.res
}
/// 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>,
/// Calls the given function once for each expression contained. This will enter bodies, but not
/// nested items.
pub fn for_each_expr_with_closures<'tcx, B, C: Continue>(
cx: &LateContext<'tcx>,
node: impl Visitable<'tcx>,
f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B, C>,
) -> Option<B> {
struct V<'tcx, B, F> {
tcx: TyCtxt<'tcx>,
f: F,
res: Option<B>,
}
impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> bool> Visitor<'tcx> for V<'tcx, F> {
impl<'tcx, B, C: Continue, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B, C>> Visitor<'tcx> for V<'tcx, B, F> {
type NestedFilter = nested_filter::OnlyBodies;
fn nested_visit_map(&mut self) -> Self::Map {
self.hir
self.tcx.hir()
}
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
if (self.f)(expr) {
walk_expr(self, expr);
fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) {
if self.res.is_some() {
return;
}
match (self.f)(e) {
ControlFlow::Continue(c) if c.descend() => walk_expr(self, e),
ControlFlow::Break(b) => self.res = Some(b),
ControlFlow::Continue(_) => (),
}
}
}
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> {
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
if (self.0)(e) {
walk_expr(self, e);
}
}
// Only walk closures
fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
// Avoid unnecessary `walk_*` calls.
fn visit_ty(&mut self, _: &'tcx hir::Ty<'tcx>) {}
fn visit_pat(&mut self, _: &'tcx Pat<'tcx>) {}
fn visit_qpath(&mut self, _: &'tcx QPath<'tcx>, _: HirId, _: Span) {}
// Avoid monomorphising all `visit_*` functions.
fn visit_nested_item(&mut self, _: ItemId) {}
}
V(f)
let mut v = V {
tcx: cx.tcx,
f,
res: None,
};
node.visit(&mut v);
v.res
}
/// returns `true` if expr contains match expr desugared from try
fn contains_try(expr: &hir::Expr<'_>) -> bool {
let mut found = false;
expr_visitor_no_bodies(|e| {
if !found {
found = matches!(e.kind, hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar));
for_each_expr(expr, |e| {
if matches!(e.kind, hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar)) {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
!found
})
.visit_expr(expr);
found
.is_some()
}
pub fn find_all_ret_expressions<'hir, F>(_cx: &LateContext<'_>, expr: &'hir hir::Expr<'hir>, callback: F) -> bool
@ -228,68 +253,29 @@ where
}
}
/// A type which can be visited.
pub trait Visitable<'tcx> {
/// Calls the corresponding `visit_*` function on the visitor.
fn visit<V: Visitor<'tcx>>(self, visitor: &mut V);
}
macro_rules! visitable_ref {
($t:ident, $f:ident) => {
impl<'tcx> Visitable<'tcx> for &'tcx $t<'tcx> {
fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
visitor.$f(self);
}
}
};
}
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);
// impl<'tcx, I: IntoIterator> Visitable<'tcx> for I
// where
// I::Item: Visitable<'tcx>,
// {
// fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
// for x in self {
// x.visit(visitor);
// }
// }
// }
/// Checks if the given resolved path is used in the given body.
pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool {
let mut found = false;
expr_visitor(cx, |e| {
if found {
return false;
}
for_each_expr_with_closures(cx, cx.tcx.hir().body(body).value, |e| {
if let ExprKind::Path(p) = &e.kind {
if cx.qpath_res(p, e.hir_id) == res {
found = true;
return ControlFlow::Break(());
}
}
!found
ControlFlow::Continue(())
})
.visit_expr(cx.tcx.hir().body(body).value);
found
.is_some()
}
/// Checks if the given local is used.
pub fn is_local_used<'tcx>(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool {
let mut is_used = false;
let mut visitor = expr_visitor(cx, |expr| {
if !is_used {
is_used = path_to_local_id(expr, id);
for_each_expr_with_closures(cx, visitable, |e| {
if path_to_local_id(e, id) {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
!is_used
});
visitable.visit(&mut visitor);
drop(visitor);
is_used
})
.is_some()
}
/// Checks if the given expression is a constant.