Auto merge of #8804 - Jarcho:in_recursion, r=Alexendoo

Rework `only_used_in_recursion`

fixes #8782
fixes #8629
fixes #8560
fixes #8556

This is a complete rewrite of the lint. This loses some capabilities of the old implementation. Namely the ability to track through tuple and slice patterns, as well as the ability to trace through assignments.

The two reported bugs are fixed with this. One was caused by using the name of the method rather than resolving to the `DefId` of the called method. The second was cause by using the existence of a cycle in the dependency graph to determine whether the parameter was used in recursion even though there were other ways to create a cycle in the graph.

Implementation wise this switches from using a visitor to walking up the tree from every use of each parameter until it has been determined the parameter is used for something other than recursion. This is likely to perform better as it avoids walking the entire function a second time, and it is unlikely to walk up the HIR tree very much. Some cases would perform worse though.

cc `@buttercrab`

changelog: Scale back `only_used_in_recursion` to fix false positives
changelog: Move `only_used_in_recursion` back to `complexity`
This commit is contained in:
bors 2022-08-19 16:11:48 +00:00
commit 3a54117ffc
10 changed files with 694 additions and 693 deletions

View file

@ -252,6 +252,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(non_expressive_names::JUST_UNDERSCORES_AND_DIGITS),
LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS),
LintId::of(octal_escapes::OCTAL_ESCAPES),
LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION),
LintId::of(operators::ABSURD_EXTREME_COMPARISONS),
LintId::of(operators::ASSIGN_OP_PATTERN),
LintId::of(operators::BAD_BIT_MASK),

View file

@ -72,6 +72,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD),
LintId::of(no_effect::NO_EFFECT),
LintId::of(no_effect::UNNECESSARY_OPERATION),
LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION),
LintId::of(operators::DOUBLE_COMPARISONS),
LintId::of(operators::DURATION_SUBSEC),
LintId::of(operators::IDENTITY_OP),

View file

@ -24,7 +24,6 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![
LintId::of(mutex_atomic::MUTEX_INTEGER),
LintId::of(non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY),
LintId::of(nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES),
LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION),
LintId::of(option_if_let_else::OPTION_IF_LET_ELSE),
LintId::of(redundant_pub_crate::REDUNDANT_PUB_CRATE),
LintId::of(regex::TRIVIAL_REGEX),

View file

@ -860,7 +860,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move || Box::new(manual_bits::ManualBits::new(msrv)));
store.register_late_pass(|| Box::new(default_union_representation::DefaultUnionRepresentation));
store.register_early_pass(|| Box::new(doc_link_with_quotes::DocLinkWithQuotes));
store.register_late_pass(|| Box::new(only_used_in_recursion::OnlyUsedInRecursion));
store.register_late_pass(|| Box::new(only_used_in_recursion::OnlyUsedInRecursion::default()));
let allow_dbg_in_tests = conf.allow_dbg_in_tests;
store.register_late_pass(move || Box::new(dbg_macro::DbgMacro::new(allow_dbg_in_tests)));
let cargo_ignore_publish = conf.cargo_ignore_publish;

View file

@ -1,25 +1,16 @@
use std::collections::VecDeque;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_lint_allowed;
use itertools::{izip, Itertools};
use rustc_ast::{walk_list, Label, Mutability};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{get_expr_use_or_unification_node, get_parent_node, path_def_id, path_to_local, path_to_local_id};
use core::cell::Cell;
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData};
use rustc_hir::intravisit::{walk_expr, walk_stmt, FnKind, Visitor};
use rustc_hir::{
Arm, Block, Body, Closure, Expr, ExprKind, Guard, HirId, ImplicitSelfKind, Let, Local, Pat, PatKind, Path,
PathSegment, QPath, Stmt, StmtKind, TyKind, UnOp,
};
use rustc_hir::hir_id::HirIdMap;
use rustc_hir::{Body, Expr, ExprKind, HirId, ImplItem, ImplItemKind, Node, PatKind, TraitItem, TraitItemKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_middle::ty::{Ty, TyCtxt, TypeckResults};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::kw;
use rustc_span::symbol::Ident;
use rustc_middle::ty::subst::{GenericArgKind, SubstsRef};
use rustc_middle::ty::{self, ConstKind};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::{kw, Ident};
use rustc_span::Span;
declare_clippy_lint! {
@ -89,572 +80,323 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.61.0"]
pub ONLY_USED_IN_RECURSION,
nursery,
complexity,
"arguments that is only used in recursion can be removed"
}
declare_lint_pass!(OnlyUsedInRecursion => [ONLY_USED_IN_RECURSION]);
impl_lint_pass!(OnlyUsedInRecursion => [ONLY_USED_IN_RECURSION]);
#[derive(Clone, Copy)]
enum FnKind {
Fn,
TraitFn,
// This is a hack. Ideally we would store a `SubstsRef<'tcx>` type here, but a lint pass must be `'static`.
// Substitutions are, however, interned. This allows us to store the pointer as a `usize` when comparing for
// equality.
ImplTraitFn(usize),
}
struct Param {
/// The function this is a parameter for.
fn_id: DefId,
fn_kind: FnKind,
/// The index of this parameter.
idx: usize,
ident: Ident,
/// Whether this parameter should be linted. Set by `Params::flag_for_linting`.
apply_lint: Cell<bool>,
/// All the uses of this parameter.
uses: Vec<Usage>,
}
impl Param {
fn new(fn_id: DefId, fn_kind: FnKind, idx: usize, ident: Ident) -> Self {
Self {
fn_id,
fn_kind,
idx,
ident,
apply_lint: Cell::new(true),
uses: Vec::new(),
}
}
}
#[derive(Debug)]
struct Usage {
span: Span,
idx: usize,
}
impl Usage {
fn new(span: Span, idx: usize) -> Self {
Self { span, idx }
}
}
/// The parameters being checked by the lint, indexed by both the parameter's `HirId` and the
/// `DefId` of the function paired with the parameter's index.
#[derive(Default)]
struct Params {
params: Vec<Param>,
by_id: HirIdMap<usize>,
by_fn: FxHashMap<(DefId, usize), usize>,
}
impl Params {
fn insert(&mut self, param: Param, id: HirId) {
let idx = self.params.len();
self.by_id.insert(id, idx);
self.by_fn.insert((param.fn_id, param.idx), idx);
self.params.push(param);
}
fn remove_by_id(&mut self, id: HirId) {
if let Some(param) = self.get_by_id_mut(id) {
param.uses = Vec::new();
let key = (param.fn_id, param.idx);
self.by_fn.remove(&key);
self.by_id.remove(&id);
}
}
fn get_by_id_mut(&mut self, id: HirId) -> Option<&mut Param> {
self.params.get_mut(*self.by_id.get(&id)?)
}
fn get_by_fn(&self, id: DefId, idx: usize) -> Option<&Param> {
self.params.get(*self.by_fn.get(&(id, idx))?)
}
fn clear(&mut self) {
self.params.clear();
self.by_id.clear();
self.by_fn.clear();
}
/// Sets the `apply_lint` flag on each parameter.
fn flag_for_linting(&mut self) {
// Stores the list of parameters currently being resolved. Needed to avoid cycles.
let mut eval_stack = Vec::new();
for param in &self.params {
self.try_disable_lint_for_param(param, &mut eval_stack);
}
}
// Use by calling `flag_for_linting`.
fn try_disable_lint_for_param(&self, param: &Param, eval_stack: &mut Vec<usize>) -> bool {
if !param.apply_lint.get() {
true
} else if param.uses.is_empty() {
// Don't lint on unused parameters.
param.apply_lint.set(false);
true
} else if eval_stack.contains(&param.idx) {
// Already on the evaluation stack. Returning false will continue to evaluate other dependencies.
false
} else {
eval_stack.push(param.idx);
// Check all cases when used at a different parameter index.
// Needed to catch cases like: `fn f(x: u32, y: u32) { f(y, x) }`
for usage in param.uses.iter().filter(|u| u.idx != param.idx) {
if self
.get_by_fn(param.fn_id, usage.idx)
// If the parameter can't be found, then it's used for more than just recursion.
.map_or(true, |p| self.try_disable_lint_for_param(p, eval_stack))
{
param.apply_lint.set(false);
eval_stack.pop();
return true;
}
}
eval_stack.pop();
false
}
}
}
#[derive(Default)]
pub struct OnlyUsedInRecursion {
/// Track the top-level body entered. Needed to delay reporting when entering nested bodies.
entered_body: Option<HirId>,
params: Params,
}
impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion {
fn check_fn(
&mut self,
cx: &LateContext<'tcx>,
kind: FnKind<'tcx>,
decl: &'tcx rustc_hir::FnDecl<'tcx>,
body: &'tcx Body<'tcx>,
_: Span,
id: HirId,
) {
if is_lint_allowed(cx, ONLY_USED_IN_RECURSION, id) {
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'tcx>) {
if body.value.span.from_expansion() {
return;
}
if let FnKind::ItemFn(ident, ..) | FnKind::Method(ident, ..) = kind {
let def_id = id.owner.to_def_id();
let data = cx.tcx.def_path(def_id).data;
if data.len() > 1 {
match data.get(data.len() - 2) {
Some(DisambiguatedDefPathData {
data: DefPathData::Impl,
disambiguator,
}) if *disambiguator != 0 => return,
_ => {},
// `skip_params` is either `0` or `1` to skip the `self` parameter in trait functions.
// It can't be renamed, and it can't be removed without removing it from multiple functions.
let (fn_id, fn_kind, skip_params) = match get_parent_node(cx.tcx, body.value.hir_id) {
Some(Node::Item(i)) => (i.def_id.to_def_id(), FnKind::Fn, 0),
Some(Node::TraitItem(&TraitItem {
kind: TraitItemKind::Fn(ref sig, _),
def_id,
..
})) => (
def_id.to_def_id(),
FnKind::TraitFn,
if sig.decl.implicit_self.has_implicit_self() {
1
} else {
0
},
),
Some(Node::ImplItem(&ImplItem {
kind: ImplItemKind::Fn(ref sig, _),
def_id,
..
})) => {
#[allow(trivial_casts)]
if let Some(Node::Item(item)) = get_parent_node(cx.tcx, cx.tcx.hir().local_def_id_to_hir_id(def_id))
&& let Some(trait_ref) = cx.tcx.impl_trait_ref(item.def_id)
&& let Some(trait_item_id) = cx.tcx.associated_item(def_id).trait_item_def_id
{
(
trait_item_id,
FnKind::ImplTraitFn(cx.tcx.erase_regions(trait_ref.substs) as *const _ as usize),
if sig.decl.implicit_self.has_implicit_self() {
1
} else {
0
},
)
} else {
(def_id.to_def_id(), FnKind::Fn, 0)
}
}
let has_self = !matches!(decl.implicit_self, ImplicitSelfKind::None);
let ty_res = cx.typeck_results();
let param_span = body
.params
.iter()
.flat_map(|param| {
let mut v = Vec::new();
param.pat.each_binding(|_, hir_id, span, ident| {
v.push((hir_id, span, ident));
});
v
})
.skip(if has_self { 1 } else { 0 })
.filter(|(_, _, ident)| !ident.name.as_str().starts_with('_'))
.collect_vec();
let params = body.params.iter().map(|param| param.pat).collect();
let mut visitor = SideEffectVisit {
graph: FxHashMap::default(),
has_side_effect: FxHashSet::default(),
ret_vars: Vec::new(),
contains_side_effect: false,
break_vars: FxHashMap::default(),
params,
fn_ident: ident,
fn_def_id: def_id,
is_method: matches!(kind, FnKind::Method(..)),
has_self,
ty_res,
tcx: cx.tcx,
visited_exprs: FxHashSet::default(),
},
_ => return,
};
visitor.visit_expr(&body.value);
let vars = std::mem::take(&mut visitor.ret_vars);
// this would set the return variables to side effect
visitor.add_side_effect(vars);
let mut queue = visitor.has_side_effect.iter().copied().collect::<VecDeque<_>>();
// a simple BFS to check all the variables that have side effect
while let Some(id) = queue.pop_front() {
if let Some(next) = visitor.graph.get(&id) {
for i in next {
if !visitor.has_side_effect.contains(i) {
visitor.has_side_effect.insert(*i);
queue.push_back(*i);
body.params
.iter()
.enumerate()
.skip(skip_params)
.filter_map(|(idx, p)| match p.pat.kind {
PatKind::Binding(_, id, ident, None) if !ident.as_str().starts_with('_') => {
Some((id, Param::new(fn_id, fn_kind, idx, ident)))
},
_ => None,
})
.for_each(|(id, param)| self.params.insert(param, id));
if self.entered_body.is_none() {
self.entered_body = Some(body.value.hir_id);
}
}
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) {
if let Some(id) = path_to_local(e)
&& let Some(param) = self.params.get_by_id_mut(id)
{
let typeck = cx.typeck_results();
let span = e.span;
let mut e = e;
loop {
match get_expr_use_or_unification_node(cx.tcx, e) {
None | Some((Node::Stmt(_), _)) => return,
Some((Node::Expr(parent), child_id)) => match parent.kind {
// Recursive call. Track which index the parameter is used in.
ExprKind::Call(callee, args)
if path_def_id(cx, callee).map_or(false, |id| {
id == param.fn_id
&& has_matching_substs(param.fn_kind, typeck.node_substs(callee.hir_id))
}) =>
{
if let Some(idx) = args.iter().position(|arg| arg.hir_id == child_id) {
param.uses.push(Usage::new(span, idx));
}
return;
},
ExprKind::MethodCall(_, args, _)
if typeck.type_dependent_def_id(parent.hir_id).map_or(false, |id| {
id == param.fn_id
&& has_matching_substs(param.fn_kind, typeck.node_substs(parent.hir_id))
}) =>
{
if let Some(idx) = args.iter().position(|arg| arg.hir_id == child_id) {
param.uses.push(Usage::new(span, idx));
}
return;
},
// Assignment to a parameter is fine.
ExprKind::Assign(lhs, _, _) | ExprKind::AssignOp(_, lhs, _) if lhs.hir_id == child_id => {
return;
},
// Parameter update e.g. `x = x + 1`
ExprKind::Assign(lhs, rhs, _) | ExprKind::AssignOp(_, lhs, rhs)
if rhs.hir_id == child_id && path_to_local_id(lhs, id) =>
{
return;
},
// Side-effect free expressions. Walk to the parent expression.
ExprKind::Binary(_, lhs, rhs)
if typeck.expr_ty(lhs).is_primitive() && typeck.expr_ty(rhs).is_primitive() =>
{
e = parent;
continue;
},
ExprKind::Unary(_, arg) if typeck.expr_ty(arg).is_primitive() => {
e = parent;
continue;
},
ExprKind::AddrOf(..) | ExprKind::Cast(..) => {
e = parent;
continue;
},
// Only allow field accesses without auto-deref
ExprKind::Field(..) if typeck.adjustments().get(child_id).is_none() => {
e = parent;
continue
}
_ => (),
},
_ => (),
}
self.params.remove_by_id(id);
return;
}
}
}
for (id, span, ident) in param_span {
// if the variable is not used in recursion, it would be marked as unused
if !visitor.has_side_effect.contains(&id) {
let mut queue = VecDeque::new();
let mut visited = FxHashSet::default();
queue.push_back(id);
// a simple BFS to check the graph can reach to itself
// if it can't, it means the variable is never used in recursion
while let Some(id) = queue.pop_front() {
if let Some(next) = visitor.graph.get(&id) {
for i in next {
if !visited.contains(i) {
visited.insert(id);
queue.push_back(*i);
}
}
}
}
if visited.contains(&id) {
span_lint_and_sugg(
fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'tcx>) {
if self.entered_body == Some(body.value.hir_id) {
self.entered_body = None;
self.params.flag_for_linting();
for param in &self.params.params {
if param.apply_lint.get() {
span_lint_and_then(
cx,
ONLY_USED_IN_RECURSION,
span,
param.ident.span,
"parameter is only used in recursion",
"if this is intentional, prefix with an underscore",
format!("_{}", ident.name.as_str()),
|diag| {
if param.ident.name != kw::SelfLower {
diag.span_suggestion(
param.ident.span,
"if this is intentional, prefix it with an underscore",
format!("_{}", param.ident.name),
Applicability::MaybeIncorrect,
);
}
diag.span_note(
param.uses.iter().map(|x| x.span).collect::<Vec<_>>(),
"parameter used here",
);
},
);
}
}
self.params.clear();
}
}
}
pub fn is_primitive(ty: Ty<'_>) -> bool {
let ty = ty.peel_refs();
ty.is_primitive() || ty.is_str()
}
pub fn is_array(ty: Ty<'_>) -> bool {
let ty = ty.peel_refs();
ty.is_array() || ty.is_array_slice()
}
/// This builds the graph of side effect.
/// The edge `a -> b` means if `a` has side effect, `b` will have side effect.
///
/// There are some example in following code:
/// ```rust, ignore
/// let b = 1;
/// let a = b; // a -> b
/// let (c, d) = (a, b); // c -> b, d -> b
///
/// let e = if a == 0 { // e -> a
/// c // e -> c
/// } else {
/// d // e -> d
/// };
/// ```
pub struct SideEffectVisit<'tcx> {
graph: FxHashMap<HirId, FxHashSet<HirId>>,
has_side_effect: FxHashSet<HirId>,
// bool for if the variable was dereferenced from mutable reference
ret_vars: Vec<(HirId, bool)>,
contains_side_effect: bool,
// break label
break_vars: FxHashMap<Ident, Vec<(HirId, bool)>>,
params: Vec<&'tcx Pat<'tcx>>,
fn_ident: Ident,
fn_def_id: DefId,
is_method: bool,
has_self: bool,
ty_res: &'tcx TypeckResults<'tcx>,
tcx: TyCtxt<'tcx>,
visited_exprs: FxHashSet<HirId>,
}
impl<'tcx> Visitor<'tcx> for SideEffectVisit<'tcx> {
fn visit_stmt(&mut self, s: &'tcx Stmt<'tcx>) {
match s.kind {
StmtKind::Local(Local {
pat, init: Some(init), ..
}) => {
self.visit_pat_expr(pat, init, false);
},
StmtKind::Item(_) | StmtKind::Expr(_) | StmtKind::Semi(_) => {
walk_stmt(self, s);
},
StmtKind::Local(_) => {},
}
self.ret_vars.clear();
}
fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
if !self.visited_exprs.insert(ex.hir_id) {
return;
}
match ex.kind {
ExprKind::Array(exprs) | ExprKind::Tup(exprs) => {
self.ret_vars = exprs
.iter()
.flat_map(|expr| {
self.visit_expr(expr);
std::mem::take(&mut self.ret_vars)
})
.collect();
},
ExprKind::Call(callee, args) => self.visit_fn(callee, args),
ExprKind::MethodCall(path, args, _) => self.visit_method_call(path, args),
ExprKind::Binary(_, lhs, rhs) => {
self.visit_bin_op(lhs, rhs);
},
ExprKind::Unary(op, expr) => self.visit_un_op(op, expr),
ExprKind::Let(Let { pat, init, .. }) => self.visit_pat_expr(pat, init, false),
ExprKind::If(bind, then_expr, else_expr) => {
self.visit_if(bind, then_expr, else_expr);
},
ExprKind::Match(expr, arms, _) => self.visit_match(expr, arms),
// since analysing the closure is not easy, just set all variables in it to side-effect
ExprKind::Closure(&Closure { body, .. }) => {
let body = self.tcx.hir().body(body);
self.visit_body(body);
let vars = std::mem::take(&mut self.ret_vars);
self.add_side_effect(vars);
},
ExprKind::Loop(block, label, _, _) | ExprKind::Block(block, label) => {
self.visit_block_label(block, label);
},
ExprKind::Assign(bind, expr, _) => {
self.visit_assign(bind, expr);
},
ExprKind::AssignOp(_, bind, expr) => {
self.visit_assign(bind, expr);
self.visit_bin_op(bind, expr);
},
ExprKind::Field(expr, _) => {
self.visit_expr(expr);
if matches!(self.ty_res.expr_ty(expr).kind(), ty::Ref(_, _, Mutability::Mut)) {
self.ret_vars.iter_mut().for_each(|(_, b)| *b = true);
}
},
ExprKind::Index(expr, index) => {
self.visit_expr(expr);
let mut vars = std::mem::take(&mut self.ret_vars);
self.visit_expr(index);
self.ret_vars.append(&mut vars);
if !is_array(self.ty_res.expr_ty(expr)) {
self.add_side_effect(self.ret_vars.clone());
} else if matches!(self.ty_res.expr_ty(expr).kind(), ty::Ref(_, _, Mutability::Mut)) {
self.ret_vars.iter_mut().for_each(|(_, b)| *b = true);
}
},
ExprKind::Break(dest, Some(expr)) => {
self.visit_expr(expr);
if let Some(label) = dest.label {
self.break_vars
.entry(label.ident)
.or_insert(Vec::new())
.append(&mut self.ret_vars);
}
self.contains_side_effect = true;
},
ExprKind::Ret(Some(expr)) => {
self.visit_expr(expr);
let vars = std::mem::take(&mut self.ret_vars);
self.add_side_effect(vars);
self.contains_side_effect = true;
},
ExprKind::Break(_, None) | ExprKind::Continue(_) | ExprKind::Ret(None) => {
self.contains_side_effect = true;
},
ExprKind::Struct(_, exprs, expr) => {
let mut ret_vars = exprs
.iter()
.flat_map(|field| {
self.visit_expr(field.expr);
std::mem::take(&mut self.ret_vars)
})
.collect();
walk_list!(self, visit_expr, expr);
self.ret_vars.append(&mut ret_vars);
},
_ => walk_expr(self, ex),
}
}
fn visit_path(&mut self, path: &'tcx Path<'tcx>, _id: HirId) {
if let Res::Local(id) = path.res {
self.ret_vars.push((id, false));
}
}
}
impl<'tcx> SideEffectVisit<'tcx> {
fn visit_assign(&mut self, lhs: &'tcx Expr<'tcx>, rhs: &'tcx Expr<'tcx>) {
// Just support array and tuple unwrapping for now.
//
// ex) `(a, b) = (c, d);`
// The graph would look like this:
// a -> c
// b -> d
//
// This would minimize the connection of the side-effect graph.
match (&lhs.kind, &rhs.kind) {
(ExprKind::Array(lhs), ExprKind::Array(rhs)) | (ExprKind::Tup(lhs), ExprKind::Tup(rhs)) => {
// if not, it is a compile error
debug_assert!(lhs.len() == rhs.len());
izip!(*lhs, *rhs).for_each(|(lhs, rhs)| self.visit_assign(lhs, rhs));
},
// in other assigns, we have to connect all each other
// because they can be connected somehow
_ => {
self.visit_expr(lhs);
let lhs_vars = std::mem::take(&mut self.ret_vars);
self.visit_expr(rhs);
let rhs_vars = std::mem::take(&mut self.ret_vars);
self.connect_assign(&lhs_vars, &rhs_vars, false);
},
}
}
fn visit_block_label(&mut self, block: &'tcx Block<'tcx>, label: Option<Label>) {
self.visit_block(block);
let _ = label.and_then(|label| {
self.break_vars
.remove(&label.ident)
.map(|mut break_vars| self.ret_vars.append(&mut break_vars))
});
}
fn visit_bin_op(&mut self, lhs: &'tcx Expr<'tcx>, rhs: &'tcx Expr<'tcx>) {
self.visit_expr(lhs);
let mut ret_vars = std::mem::take(&mut self.ret_vars);
self.visit_expr(rhs);
self.ret_vars.append(&mut ret_vars);
// the binary operation between non primitive values are overloaded operators
// so they can have side-effects
if !is_primitive(self.ty_res.expr_ty(lhs)) || !is_primitive(self.ty_res.expr_ty(rhs)) {
self.ret_vars.iter().for_each(|id| {
self.has_side_effect.insert(id.0);
});
self.contains_side_effect = true;
}
}
fn visit_un_op(&mut self, op: UnOp, expr: &'tcx Expr<'tcx>) {
self.visit_expr(expr);
let ty = self.ty_res.expr_ty(expr);
// dereferencing a reference has no side-effect
if !is_primitive(ty) && !matches!((op, ty.kind()), (UnOp::Deref, ty::Ref(..))) {
self.add_side_effect(self.ret_vars.clone());
}
if matches!((op, ty.kind()), (UnOp::Deref, ty::Ref(_, _, Mutability::Mut))) {
self.ret_vars.iter_mut().for_each(|(_, b)| *b = true);
}
}
fn visit_pat_expr(&mut self, pat: &'tcx Pat<'tcx>, expr: &'tcx Expr<'tcx>, connect_self: bool) {
match (&pat.kind, &expr.kind) {
(PatKind::Tuple(pats, _), ExprKind::Tup(exprs)) => {
self.ret_vars = izip!(*pats, *exprs)
.flat_map(|(pat, expr)| {
self.visit_pat_expr(pat, expr, connect_self);
std::mem::take(&mut self.ret_vars)
})
.collect();
},
(PatKind::Slice(front_exprs, _, back_exprs), ExprKind::Array(exprs)) => {
let mut vars = izip!(*front_exprs, *exprs)
.flat_map(|(pat, expr)| {
self.visit_pat_expr(pat, expr, connect_self);
std::mem::take(&mut self.ret_vars)
})
.collect();
self.ret_vars = izip!(back_exprs.iter().rev(), exprs.iter().rev())
.flat_map(|(pat, expr)| {
self.visit_pat_expr(pat, expr, connect_self);
std::mem::take(&mut self.ret_vars)
})
.collect();
self.ret_vars.append(&mut vars);
},
_ => {
let mut lhs_vars = Vec::new();
pat.each_binding(|_, id, _, _| lhs_vars.push((id, false)));
self.visit_expr(expr);
let rhs_vars = std::mem::take(&mut self.ret_vars);
self.connect_assign(&lhs_vars, &rhs_vars, connect_self);
self.ret_vars = rhs_vars;
},
}
}
fn visit_fn(&mut self, callee: &'tcx Expr<'tcx>, args: &'tcx [Expr<'tcx>]) {
self.visit_expr(callee);
let mut ret_vars = std::mem::take(&mut self.ret_vars);
self.add_side_effect(ret_vars.clone());
let mut is_recursive = false;
if_chain! {
if !self.has_self;
if let ExprKind::Path(QPath::Resolved(_, path)) = callee.kind;
if let Res::Def(DefKind::Fn, def_id) = path.res;
if self.fn_def_id == def_id;
then {
is_recursive = true;
}
}
if_chain! {
if !self.has_self && self.is_method;
if let ExprKind::Path(QPath::TypeRelative(ty, segment)) = callee.kind;
if segment.ident == self.fn_ident;
if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind;
if let Res::SelfTy{ .. } = path.res;
then {
is_recursive = true;
}
}
if is_recursive {
izip!(self.params.clone(), args).for_each(|(pat, expr)| {
self.visit_pat_expr(pat, expr, true);
self.ret_vars.clear();
});
} else {
// This would set arguments used in closure that does not have side-effect.
// Closure itself can be detected whether there is a side-effect, but the
// value of variable that is holding closure can change.
// So, we just check the variables.
self.ret_vars = args
.iter()
.flat_map(|expr| {
self.visit_expr(expr);
std::mem::take(&mut self.ret_vars)
})
.collect_vec()
.into_iter()
.map(|id| {
self.has_side_effect.insert(id.0);
id
})
.collect();
self.contains_side_effect = true;
}
self.ret_vars.append(&mut ret_vars);
}
fn visit_method_call(&mut self, path: &'tcx PathSegment<'tcx>, args: &'tcx [Expr<'tcx>]) {
if_chain! {
if self.is_method;
if path.ident == self.fn_ident;
if let ExprKind::Path(QPath::Resolved(_, path)) = args.first().unwrap().kind;
if let Res::Local(..) = path.res;
let ident = path.segments.last().unwrap().ident;
if ident.name == kw::SelfLower;
then {
izip!(self.params.clone(), args.iter())
.for_each(|(pat, expr)| {
self.visit_pat_expr(pat, expr, true);
self.ret_vars.clear();
});
} else {
self.ret_vars = args
.iter()
.flat_map(|expr| {
self.visit_expr(expr);
std::mem::take(&mut self.ret_vars)
})
.collect_vec()
.into_iter()
.map(|a| {
self.has_side_effect.insert(a.0);
a
})
.collect();
self.contains_side_effect = true;
}
}
}
fn visit_if(&mut self, bind: &'tcx Expr<'tcx>, then_expr: &'tcx Expr<'tcx>, else_expr: Option<&'tcx Expr<'tcx>>) {
let contains_side_effect = self.contains_side_effect;
self.contains_side_effect = false;
self.visit_expr(bind);
let mut vars = std::mem::take(&mut self.ret_vars);
self.visit_expr(then_expr);
let mut then_vars = std::mem::take(&mut self.ret_vars);
walk_list!(self, visit_expr, else_expr);
if self.contains_side_effect {
self.add_side_effect(vars.clone());
}
self.contains_side_effect |= contains_side_effect;
self.ret_vars.append(&mut vars);
self.ret_vars.append(&mut then_vars);
}
fn visit_match(&mut self, expr: &'tcx Expr<'tcx>, arms: &'tcx [Arm<'tcx>]) {
self.visit_expr(expr);
let mut expr_vars = std::mem::take(&mut self.ret_vars);
self.ret_vars = arms
.iter()
.flat_map(|arm| {
let contains_side_effect = self.contains_side_effect;
self.contains_side_effect = false;
// this would visit `expr` multiple times
// but couldn't think of a better way
self.visit_pat_expr(arm.pat, expr, false);
let mut vars = std::mem::take(&mut self.ret_vars);
let _ = arm.guard.as_ref().map(|guard| {
self.visit_expr(match guard {
Guard::If(expr) | Guard::IfLet(Let { init: expr, .. }) => expr,
});
vars.append(&mut self.ret_vars);
});
self.visit_expr(arm.body);
if self.contains_side_effect {
self.add_side_effect(vars.clone());
self.add_side_effect(expr_vars.clone());
}
self.contains_side_effect |= contains_side_effect;
vars.append(&mut self.ret_vars);
vars
})
.collect();
self.ret_vars.append(&mut expr_vars);
}
fn connect_assign(&mut self, lhs: &[(HirId, bool)], rhs: &[(HirId, bool)], connect_self: bool) {
// if mutable dereference is on assignment it can have side-effect
// (this can lead to parameter mutable dereference and change the original value)
// too hard to detect whether this value is from parameter, so this would all
// check mutable dereference assignment to side effect
lhs.iter().filter(|(_, b)| *b).for_each(|(id, _)| {
self.has_side_effect.insert(*id);
self.contains_side_effect = true;
});
// there is no connection
if lhs.is_empty() || rhs.is_empty() {
return;
}
// by connected rhs in cycle, the connections would decrease
// from `n * m` to `n + m`
// where `n` and `m` are length of `lhs` and `rhs`.
// unwrap is possible since rhs is not empty
let rhs_first = rhs.first().unwrap();
for (id, _) in lhs.iter() {
if connect_self || *id != rhs_first.0 {
self.graph
.entry(*id)
.or_insert_with(FxHashSet::default)
.insert(rhs_first.0);
}
}
let rhs = rhs.iter();
izip!(rhs.clone().cycle().skip(1), rhs).for_each(|(from, to)| {
if connect_self || from.0 != to.0 {
self.graph.entry(from.0).or_insert_with(FxHashSet::default).insert(to.0);
}
});
}
fn add_side_effect(&mut self, v: Vec<(HirId, bool)>) {
for (id, _) in v {
self.has_side_effect.insert(id);
self.contains_side_effect = true;
}
fn has_matching_substs(kind: FnKind, substs: SubstsRef<'_>) -> bool {
match kind {
FnKind::Fn => true,
FnKind::TraitFn => substs.iter().enumerate().all(|(idx, subst)| match subst.unpack() {
GenericArgKind::Lifetime(_) => true,
GenericArgKind::Type(ty) => matches!(*ty.kind(), ty::Param(ty) if ty.index as usize == idx),
GenericArgKind::Const(c) => matches!(c.kind(), ConstKind::Param(c) if c.index as usize == idx),
}),
#[allow(trivial_casts)]
FnKind::ImplTraitFn(expected_substs) => substs as *const _ as usize == expected_substs,
}
}

View file

@ -48,15 +48,15 @@ impl_lint_pass!(RedundantStaticLifetimes => [REDUNDANT_STATIC_LIFETIMES]);
impl RedundantStaticLifetimes {
// Recursively visit types
fn visit_type(&mut self, ty: &Ty, cx: &EarlyContext<'_>, reason: &str) {
fn visit_type(ty: &Ty, cx: &EarlyContext<'_>, reason: &str) {
match ty.kind {
// Be careful of nested structures (arrays and tuples)
TyKind::Array(ref ty, _) | TyKind::Slice(ref ty) => {
self.visit_type(ty, cx, reason);
Self::visit_type(ty, cx, reason);
},
TyKind::Tup(ref tup) => {
for tup_ty in tup {
self.visit_type(tup_ty, cx, reason);
Self::visit_type(tup_ty, cx, reason);
}
},
// This is what we are looking for !
@ -87,7 +87,7 @@ impl RedundantStaticLifetimes {
_ => {},
}
}
self.visit_type(&borrow_type.ty, cx, reason);
Self::visit_type(&borrow_type.ty, cx, reason);
},
_ => {},
}
@ -102,13 +102,13 @@ impl EarlyLintPass for RedundantStaticLifetimes {
if !item.span.from_expansion() {
if let ItemKind::Const(_, ref var_type, _) = item.kind {
self.visit_type(var_type, cx, "constants have by default a `'static` lifetime");
Self::visit_type(var_type, cx, "constants have by default a `'static` lifetime");
// Don't check associated consts because `'static` cannot be elided on those (issue
// #2438)
}
if let ItemKind::Static(ref var_type, _, _) = item.kind {
self.visit_type(var_type, cx, "statics have by default a `'static` lifetime");
Self::visit_type(var_type, cx, "statics have by default a `'static` lifetime");
}
}
}

View file

@ -1,122 +1,113 @@
#![warn(clippy::only_used_in_recursion)]
fn simple(a: usize, b: usize) -> usize {
if a == 0 { 1 } else { simple(a - 1, b) }
fn _simple(x: u32) -> u32 {
x
}
fn with_calc(a: usize, b: isize) -> usize {
if a == 0 { 1 } else { with_calc(a - 1, -b + 1) }
fn _simple2(x: u32) -> u32 {
_simple(x)
}
fn tuple((a, b): (usize, usize)) -> usize {
if a == 0 { 1 } else { tuple((a - 1, b + 1)) }
fn _one_unused(flag: u32, a: usize) -> usize {
if flag == 0 { 0 } else { _one_unused(flag - 1, a) }
}
fn let_tuple(a: usize, b: usize) -> usize {
let (c, d) = (a, b);
if c == 0 { 1 } else { let_tuple(c - 1, d + 1) }
fn _two_unused(flag: u32, a: u32, b: i32) -> usize {
if flag == 0 { 0 } else { _two_unused(flag - 1, a, b) }
}
fn array([a, b]: [usize; 2]) -> usize {
if a == 0 { 1 } else { array([a - 1, b + 1]) }
}
fn index(a: usize, mut b: &[usize], c: usize) -> usize {
if a == 0 { 1 } else { index(a - 1, b, c + b[0]) }
}
fn break_(a: usize, mut b: usize, mut c: usize) -> usize {
let c = loop {
b += 1;
c += 1;
if c == 10 {
break b;
fn _with_calc(flag: u32, a: i64) -> usize {
if flag == 0 {
0
} else {
_with_calc(flag - 1, (-a + 10) * 5)
}
};
if a == 0 { 1 } else { break_(a - 1, c, c) }
}
// this has a side effect
fn mut_ref(a: usize, b: &mut usize) -> usize {
*b = 1;
if a == 0 { 1 } else { mut_ref(a - 1, b) }
// Don't lint
fn _used_with_flag(flag: u32, a: u32) -> usize {
if flag == 0 { 0 } else { _used_with_flag(flag ^ a, a - 1) }
}
fn mut_ref2(a: usize, b: &mut usize) -> usize {
let mut c = *b;
if a == 0 { 1 } else { mut_ref2(a - 1, &mut c) }
fn _used_with_unused(flag: u32, a: i32, b: i32) -> usize {
if flag == 0 {
0
} else {
_used_with_unused(flag - 1, -a, a + b)
}
}
fn not_primitive(a: usize, b: String) -> usize {
if a == 0 { 1 } else { not_primitive(a - 1, b) }
fn _codependent_unused(flag: u32, a: i32, b: i32) -> usize {
if flag == 0 {
0
} else {
_codependent_unused(flag - 1, a * b, a + b)
}
}
// this doesn't have a side effect,
// but `String` is not primitive.
fn not_primitive_op(a: usize, b: String, c: &str) -> usize {
if a == 1 { 1 } else { not_primitive_op(a, b + c, c) }
fn _not_primitive(flag: u32, b: String) -> usize {
if flag == 0 { 0 } else { _not_primitive(flag - 1, b) }
}
struct A;
impl A {
fn method(a: usize, b: usize) -> usize {
if a == 0 { 1 } else { A::method(a - 1, b - 1) }
fn _method(flag: usize, a: usize) -> usize {
if flag == 0 { 0 } else { Self::_method(flag - 1, a) }
}
fn method2(&self, a: usize, b: usize) -> usize {
if a == 0 { 1 } else { self.method2(a - 1, b + 1) }
fn _method_self(&self, flag: usize, a: usize) -> usize {
if flag == 0 { 0 } else { self._method_self(flag - 1, a) }
}
}
trait B {
fn hello(a: usize, b: usize) -> usize;
fn hello2(&self, a: usize, b: usize) -> usize;
fn method(flag: u32, a: usize) -> usize;
fn method_self(&self, flag: u32, a: usize) -> usize;
}
impl B for A {
fn hello(a: usize, b: usize) -> usize {
if a == 0 { 1 } else { A::hello(a - 1, b + 1) }
fn method(flag: u32, a: usize) -> usize {
if flag == 0 { 0 } else { Self::method(flag - 1, a) }
}
fn hello2(&self, a: usize, b: usize) -> usize {
if a == 0 { 1 } else { self.hello2(a - 1, b + 1) }
fn method_self(&self, flag: u32, a: usize) -> usize {
if flag == 0 { 0 } else { self.method_self(flag - 1, a) }
}
}
impl B for () {
fn method(flag: u32, a: usize) -> usize {
if flag == 0 { 0 } else { a }
}
fn method_self(&self, flag: u32, a: usize) -> usize {
if flag == 0 { 0 } else { a }
}
}
impl B for u32 {
fn method(flag: u32, a: usize) -> usize {
if flag == 0 { 0 } else { <() as B>::method(flag, a) }
}
fn method_self(&self, flag: u32, a: usize) -> usize {
if flag == 0 { 0 } else { ().method_self(flag, a) }
}
}
trait C {
fn hello(a: usize, b: usize) -> usize {
if a == 0 { 1 } else { Self::hello(a - 1, b + 1) }
fn method(flag: u32, a: usize) -> usize {
if flag == 0 { 0 } else { Self::method(flag - 1, a) }
}
fn hello2(&self, a: usize, b: usize) -> usize {
if a == 0 { 1 } else { self.hello2(a - 1, b + 1) }
fn method_self(&self, flag: u32, a: usize) -> usize {
if flag == 0 { 0 } else { self.method_self(flag - 1, a) }
}
}
fn ignore(a: usize, _: usize) -> usize {
if a == 1 { 1 } else { ignore(a - 1, 0) }
}
fn ignore2(a: usize, _b: usize) -> usize {
if a == 1 { 1 } else { ignore2(a - 1, _b) }
}
fn f1(a: u32) -> u32 {
a
}
fn f2(a: u32) -> u32 {
f1(a)
}
fn inner_fn(a: u32) -> u32 {
fn inner_fn(a: u32) -> u32 {
a
}
inner_fn(a)
fn _ignore(flag: usize, _a: usize) -> usize {
if flag == 0 { 0 } else { _ignore(flag - 1, _a) }
}
fn main() {}

View file

@ -1,82 +1,195 @@
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:3:21
--> $DIR/only_used_in_recursion.rs:11:27
|
LL | fn simple(a: usize, b: usize) -> usize {
| ^ help: if this is intentional, prefix with an underscore: `_b`
LL | fn _one_unused(flag: u32, a: usize) -> usize {
| ^ help: if this is intentional, prefix it with an underscore: `_a`
|
= note: `-D clippy::only-used-in-recursion` implied by `-D warnings`
note: parameter used here
--> $DIR/only_used_in_recursion.rs:12:53
|
LL | if flag == 0 { 0 } else { _one_unused(flag - 1, a) }
| ^
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:7:24
--> $DIR/only_used_in_recursion.rs:15:27
|
LL | fn with_calc(a: usize, b: isize) -> usize {
| ^ help: if this is intentional, prefix with an underscore: `_b`
LL | fn _two_unused(flag: u32, a: u32, b: i32) -> usize {
| ^ help: if this is intentional, prefix it with an underscore: `_a`
|
note: parameter used here
--> $DIR/only_used_in_recursion.rs:16:53
|
LL | if flag == 0 { 0 } else { _two_unused(flag - 1, a, b) }
| ^
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:11:14
--> $DIR/only_used_in_recursion.rs:15:35
|
LL | fn tuple((a, b): (usize, usize)) -> usize {
| ^ help: if this is intentional, prefix with an underscore: `_b`
LL | fn _two_unused(flag: u32, a: u32, b: i32) -> usize {
| ^ help: if this is intentional, prefix it with an underscore: `_b`
|
note: parameter used here
--> $DIR/only_used_in_recursion.rs:16:56
|
LL | if flag == 0 { 0 } else { _two_unused(flag - 1, a, b) }
| ^
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:15:24
--> $DIR/only_used_in_recursion.rs:19:26
|
LL | fn let_tuple(a: usize, b: usize) -> usize {
| ^ help: if this is intentional, prefix with an underscore: `_b`
LL | fn _with_calc(flag: u32, a: i64) -> usize {
| ^ help: if this is intentional, prefix it with an underscore: `_a`
|
note: parameter used here
--> $DIR/only_used_in_recursion.rs:23:32
|
LL | _with_calc(flag - 1, (-a + 10) * 5)
| ^
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:20:14
--> $DIR/only_used_in_recursion.rs:32:33
|
LL | fn array([a, b]: [usize; 2]) -> usize {
| ^ help: if this is intentional, prefix with an underscore: `_b`
LL | fn _used_with_unused(flag: u32, a: i32, b: i32) -> usize {
| ^ help: if this is intentional, prefix it with an underscore: `_a`
|
note: parameter used here
--> $DIR/only_used_in_recursion.rs:36:38
|
LL | _used_with_unused(flag - 1, -a, a + b)
| ^ ^
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:24:20
--> $DIR/only_used_in_recursion.rs:32:41
|
LL | fn index(a: usize, mut b: &[usize], c: usize) -> usize {
| ^^^^^ help: if this is intentional, prefix with an underscore: `_b`
LL | fn _used_with_unused(flag: u32, a: i32, b: i32) -> usize {
| ^ help: if this is intentional, prefix it with an underscore: `_b`
|
note: parameter used here
--> $DIR/only_used_in_recursion.rs:36:45
|
LL | _used_with_unused(flag - 1, -a, a + b)
| ^
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:24:37
--> $DIR/only_used_in_recursion.rs:40:35
|
LL | fn index(a: usize, mut b: &[usize], c: usize) -> usize {
| ^ help: if this is intentional, prefix with an underscore: `_c`
LL | fn _codependent_unused(flag: u32, a: i32, b: i32) -> usize {
| ^ help: if this is intentional, prefix it with an underscore: `_a`
|
note: parameter used here
--> $DIR/only_used_in_recursion.rs:44:39
|
LL | _codependent_unused(flag - 1, a * b, a + b)
| ^ ^
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:28:21
--> $DIR/only_used_in_recursion.rs:40:43
|
LL | fn break_(a: usize, mut b: usize, mut c: usize) -> usize {
| ^^^^^ help: if this is intentional, prefix with an underscore: `_b`
LL | fn _codependent_unused(flag: u32, a: i32, b: i32) -> usize {
| ^ help: if this is intentional, prefix it with an underscore: `_b`
|
note: parameter used here
--> $DIR/only_used_in_recursion.rs:44:43
|
LL | _codependent_unused(flag - 1, a * b, a + b)
| ^ ^
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:46:23
--> $DIR/only_used_in_recursion.rs:48:30
|
LL | fn mut_ref2(a: usize, b: &mut usize) -> usize {
| ^ help: if this is intentional, prefix with an underscore: `_b`
LL | fn _not_primitive(flag: u32, b: String) -> usize {
| ^ help: if this is intentional, prefix it with an underscore: `_b`
|
note: parameter used here
--> $DIR/only_used_in_recursion.rs:49:56
|
LL | if flag == 0 { 0 } else { _not_primitive(flag - 1, b) }
| ^
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:51:28
--> $DIR/only_used_in_recursion.rs:55:29
|
LL | fn not_primitive(a: usize, b: String) -> usize {
| ^ help: if this is intentional, prefix with an underscore: `_b`
LL | fn _method(flag: usize, a: usize) -> usize {
| ^ help: if this is intentional, prefix it with an underscore: `_a`
|
note: parameter used here
--> $DIR/only_used_in_recursion.rs:56:59
|
LL | if flag == 0 { 0 } else { Self::_method(flag - 1, a) }
| ^
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:68:33
--> $DIR/only_used_in_recursion.rs:59:22
|
LL | fn method2(&self, a: usize, b: usize) -> usize {
| ^ help: if this is intentional, prefix with an underscore: `_b`
LL | fn _method_self(&self, flag: usize, a: usize) -> usize {
| ^^^^
|
note: parameter used here
--> $DIR/only_used_in_recursion.rs:60:35
|
LL | if flag == 0 { 0 } else { self._method_self(flag - 1, a) }
| ^^^^
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:90:24
--> $DIR/only_used_in_recursion.rs:59:41
|
LL | fn hello(a: usize, b: usize) -> usize {
| ^ help: if this is intentional, prefix with an underscore: `_b`
LL | fn _method_self(&self, flag: usize, a: usize) -> usize {
| ^ help: if this is intentional, prefix it with an underscore: `_a`
|
note: parameter used here
--> $DIR/only_used_in_recursion.rs:60:63
|
LL | if flag == 0 { 0 } else { self._method_self(flag - 1, a) }
| ^
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:94:32
--> $DIR/only_used_in_recursion.rs:70:26
|
LL | fn hello2(&self, a: usize, b: usize) -> usize {
| ^ help: if this is intentional, prefix with an underscore: `_b`
LL | fn method(flag: u32, a: usize) -> usize {
| ^ help: if this is intentional, prefix it with an underscore: `_a`
|
note: parameter used here
--> $DIR/only_used_in_recursion.rs:71:58
|
LL | if flag == 0 { 0 } else { Self::method(flag - 1, a) }
| ^
error: aborting due to 13 previous errors
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:74:38
|
LL | fn method_self(&self, flag: u32, a: usize) -> usize {
| ^ help: if this is intentional, prefix it with an underscore: `_a`
|
note: parameter used here
--> $DIR/only_used_in_recursion.rs:75:62
|
LL | if flag == 0 { 0 } else { self.method_self(flag - 1, a) }
| ^
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:100:26
|
LL | fn method(flag: u32, a: usize) -> usize {
| ^ help: if this is intentional, prefix it with an underscore: `_a`
|
note: parameter used here
--> $DIR/only_used_in_recursion.rs:101:58
|
LL | if flag == 0 { 0 } else { Self::method(flag - 1, a) }
| ^
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:104:38
|
LL | fn method_self(&self, flag: u32, a: usize) -> usize {
| ^ help: if this is intentional, prefix it with an underscore: `_a`
|
note: parameter used here
--> $DIR/only_used_in_recursion.rs:105:62
|
LL | if flag == 0 { 0 } else { self.method_self(flag - 1, a) }
| ^
error: aborting due to 16 previous errors

View file

@ -0,0 +1,91 @@
#![warn(clippy::only_used_in_recursion)]
fn _with_inner(flag: u32, a: u32, b: u32) -> usize {
fn inner(flag: u32, a: u32) -> u32 {
if flag == 0 { 0 } else { inner(flag, a) }
}
let x = inner(flag, a);
if flag == 0 { 0 } else { _with_inner(flag, a, b + x) }
}
fn _with_closure(a: Option<u32>, b: u32, f: impl Fn(u32, u32) -> Option<u32>) -> u32 {
if let Some(x) = a.and_then(|x| f(x, x)) {
_with_closure(Some(x), b, f)
} else {
0
}
}
// Issue #8560
trait D {
fn foo(&mut self, arg: u32) -> u32;
}
mod m {
pub struct S(u32);
impl S {
pub fn foo(&mut self, arg: u32) -> u32 {
arg + self.0
}
}
}
impl D for m::S {
fn foo(&mut self, arg: u32) -> u32 {
self.foo(arg)
}
}
// Issue #8782
fn only_let(x: u32) {
let y = 10u32;
let _z = x * y;
}
trait E<T: E<()>> {
fn method(flag: u32, a: usize) -> usize {
if flag == 0 {
0
} else {
<T as E<()>>::method(flag - 1, a)
}
}
}
impl E<()> for () {
fn method(flag: u32, a: usize) -> usize {
if flag == 0 { 0 } else { a }
}
}
fn overwritten_param(flag: u32, mut a: usize) -> usize {
if flag == 0 {
return 0;
} else if flag > 5 {
a += flag as usize;
} else {
a = 5;
}
overwritten_param(flag, a)
}
fn field_direct(flag: u32, mut a: (usize,)) -> usize {
if flag == 0 {
0
} else {
a.0 += 5;
field_direct(flag - 1, a)
}
}
fn field_deref(flag: u32, a: &mut Box<(usize,)>) -> usize {
if flag == 0 {
0
} else {
a.0 += 5;
field_deref(flag - 1, a)
}
}
fn main() {}

View file

@ -0,0 +1,63 @@
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion2.rs:3:35
|
LL | fn _with_inner(flag: u32, a: u32, b: u32) -> usize {
| ^ help: if this is intentional, prefix it with an underscore: `_b`
|
= note: `-D clippy::only-used-in-recursion` implied by `-D warnings`
note: parameter used here
--> $DIR/only_used_in_recursion2.rs:9:52
|
LL | if flag == 0 { 0 } else { _with_inner(flag, a, b + x) }
| ^
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion2.rs:4:25
|
LL | fn inner(flag: u32, a: u32) -> u32 {
| ^ help: if this is intentional, prefix it with an underscore: `_a`
|
note: parameter used here
--> $DIR/only_used_in_recursion2.rs:5:47
|
LL | if flag == 0 { 0 } else { inner(flag, a) }
| ^
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion2.rs:12:34
|
LL | fn _with_closure(a: Option<u32>, b: u32, f: impl Fn(u32, u32) -> Option<u32>) -> u32 {
| ^ help: if this is intentional, prefix it with an underscore: `_b`
|
note: parameter used here
--> $DIR/only_used_in_recursion2.rs:14:32
|
LL | _with_closure(Some(x), b, f)
| ^
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion2.rs:62:37
|
LL | fn overwritten_param(flag: u32, mut a: usize) -> usize {
| ^ help: if this is intentional, prefix it with an underscore: `_a`
|
note: parameter used here
--> $DIR/only_used_in_recursion2.rs:70:29
|
LL | overwritten_param(flag, a)
| ^
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion2.rs:73:32
|
LL | fn field_direct(flag: u32, mut a: (usize,)) -> usize {
| ^ help: if this is intentional, prefix it with an underscore: `_a`
|
note: parameter used here
--> $DIR/only_used_in_recursion2.rs:78:32
|
LL | field_direct(flag - 1, a)
| ^
error: aborting due to 5 previous errors