mirror of
https://github.com/rust-lang/rust-clippy
synced 2024-11-10 23:24:24 +00:00
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:
commit
3a54117ffc
10 changed files with 694 additions and 693 deletions
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(¶m.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,
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
cx,
|
||||
ONLY_USED_IN_RECURSION,
|
||||
span,
|
||||
"parameter is only used in recursion",
|
||||
"if this is intentional, prefix with an underscore",
|
||||
format!("_{}", ident.name.as_str()),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
// `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)
|
||||
}
|
||||
},
|
||||
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
|
||||
_ => return,
|
||||
};
|
||||
body.params
|
||||
.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
|
||||
.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,
|
||||
})
|
||||
.collect();
|
||||
self.ret_vars.append(&mut expr_vars);
|
||||
.for_each(|(id, param)| self.params.insert(param, id));
|
||||
if self.entered_body.is_none() {
|
||||
self.entered_body = Some(body.value.hir_id);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 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,
|
||||
param.ident.span,
|
||||
"parameter is only used in recursion",
|
||||
|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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 _with_calc(flag: u32, a: i64) -> usize {
|
||||
if flag == 0 {
|
||||
0
|
||||
} else {
|
||||
_with_calc(flag - 1, (-a + 10) * 5)
|
||||
}
|
||||
}
|
||||
|
||||
fn index(a: usize, mut b: &[usize], c: usize) -> usize {
|
||||
if a == 0 { 1 } else { index(a - 1, b, c + b[0]) }
|
||||
// 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 break_(a: usize, mut b: usize, mut c: usize) -> usize {
|
||||
let c = loop {
|
||||
b += 1;
|
||||
c += 1;
|
||||
if c == 10 {
|
||||
break b;
|
||||
}
|
||||
};
|
||||
|
||||
if a == 0 { 1 } else { break_(a - 1, c, 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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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) }
|
||||
fn _codependent_unused(flag: u32, a: i32, b: i32) -> usize {
|
||||
if flag == 0 {
|
||||
0
|
||||
} else {
|
||||
_codependent_unused(flag - 1, a * b, a + b)
|
||||
}
|
||||
}
|
||||
|
||||
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 not_primitive(a: usize, b: String) -> usize {
|
||||
if a == 0 { 1 } else { not_primitive(a - 1, 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() {}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
91
tests/ui/only_used_in_recursion2.rs
Normal file
91
tests/ui/only_used_in_recursion2.rs
Normal 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() {}
|
63
tests/ui/only_used_in_recursion2.stderr
Normal file
63
tests/ui/only_used_in_recursion2.stderr
Normal 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
|
||||
|
Loading…
Reference in a new issue