1 use clippy_utils::diagnostics::span_lint_and_then;
2 use clippy_utils::{get_expr_use_or_unification_node, get_parent_node, path_def_id, path_to_local, path_to_local_id};
4 use rustc_data_structures::fx::FxHashMap;
5 use rustc_errors::Applicability;
6 use rustc_hir::def_id::DefId;
7 use rustc_hir::hir_id::HirIdMap;
8 use rustc_hir::{Body, Expr, ExprKind, HirId, ImplItem, ImplItemKind, Node, PatKind, TraitItem, TraitItemKind};
9 use rustc_lint::{LateContext, LateLintPass};
10 use rustc_middle::ty::subst::{GenericArgKind, SubstsRef};
11 use rustc_middle::ty::{self, ConstKind};
12 use rustc_session::{declare_tool_lint, impl_lint_pass};
13 use rustc_span::symbol::{kw, Ident};
16 declare_clippy_lint! {
18 /// Checks for arguments that are only used in recursion with no side-effects.
20 /// ### Why is this bad?
21 /// It could contain a useless calculation and can make function simpler.
23 /// The arguments can be involved in calculations and assignments but as long as
24 /// the calculations have no side-effects (function calls or mutating dereference)
25 /// and the assigned variables are also only in recursion, it is useless.
27 /// ### Known problems
28 /// Too many code paths in the linting code are currently untested and prone to produce false
29 /// positives or are prone to have performance implications.
31 /// In some cases, this would not catch all useless arguments.
34 /// fn foo(a: usize, b: usize) -> usize {
35 /// let f = |x| x + 1;
45 /// For example, the argument `b` is only used in recursion, but the lint would not catch it.
47 /// List of some examples that can not be caught:
48 /// - binary operation of non-primitive types
50 /// - some `break` relative operations
51 /// - struct pattern binding
53 /// Also, when you recurse the function name with path segments, it is not possible to detect.
57 /// fn f(a: usize, b: usize) -> usize {
65 /// # print!("{}", f(1, 1));
70 /// fn f(a: usize) -> usize {
78 /// # print!("{}", f(1));
81 #[clippy::version = "1.61.0"]
82 pub ONLY_USED_IN_RECURSION,
84 "arguments that is only used in recursion can be removed"
86 impl_lint_pass!(OnlyUsedInRecursion => [ONLY_USED_IN_RECURSION]);
88 #[derive(Clone, Copy)]
92 // This is a hack. Ideally we would store a `SubstsRef<'tcx>` type here, but a lint pass must be `'static`.
93 // Substitutions are, however, interned. This allows us to store the pointer as a `usize` when comparing for
99 /// The function this is a parameter for.
102 /// The index of this parameter.
105 /// Whether this parameter should be linted. Set by `Params::flag_for_linting`.
106 apply_lint: Cell<bool>,
107 /// All the uses of this parameter.
111 fn new(fn_id: DefId, fn_kind: FnKind, idx: usize, ident: Ident) -> Self {
117 apply_lint: Cell::new(true),
129 fn new(span: Span, idx: usize) -> Self {
134 /// The parameters being checked by the lint, indexed by both the parameter's `HirId` and the
135 /// `DefId` of the function paired with the parameter's index.
139 by_id: HirIdMap<usize>,
140 by_fn: FxHashMap<(DefId, usize), usize>,
143 fn insert(&mut self, param: Param, id: HirId) {
144 let idx = self.params.len();
145 self.by_id.insert(id, idx);
146 self.by_fn.insert((param.fn_id, param.idx), idx);
147 self.params.push(param);
150 fn remove_by_id(&mut self, id: HirId) {
151 if let Some(param) = self.get_by_id_mut(id) {
152 param.uses = Vec::new();
153 let key = (param.fn_id, param.idx);
154 self.by_fn.remove(&key);
155 self.by_id.remove(&id);
159 fn get_by_id_mut(&mut self, id: HirId) -> Option<&mut Param> {
160 self.params.get_mut(*self.by_id.get(&id)?)
163 fn get_by_fn(&self, id: DefId, idx: usize) -> Option<&Param> {
164 self.params.get(*self.by_fn.get(&(id, idx))?)
167 fn clear(&mut self) {
173 /// Sets the `apply_lint` flag on each parameter.
174 fn flag_for_linting(&mut self) {
175 // Stores the list of parameters currently being resolved. Needed to avoid cycles.
176 let mut eval_stack = Vec::new();
177 for param in &self.params {
178 self.try_disable_lint_for_param(param, &mut eval_stack);
182 // Use by calling `flag_for_linting`.
183 fn try_disable_lint_for_param(&self, param: &Param, eval_stack: &mut Vec<usize>) -> bool {
184 if !param.apply_lint.get() {
186 } else if param.uses.is_empty() {
187 // Don't lint on unused parameters.
188 param.apply_lint.set(false);
190 } else if eval_stack.contains(¶m.idx) {
191 // Already on the evaluation stack. Returning false will continue to evaluate other dependencies.
194 eval_stack.push(param.idx);
195 // Check all cases when used at a different parameter index.
196 // Needed to catch cases like: `fn f(x: u32, y: u32) { f(y, x) }`
197 for usage in param.uses.iter().filter(|u| u.idx != param.idx) {
199 .get_by_fn(param.fn_id, usage.idx)
200 // If the parameter can't be found, then it's used for more than just recursion.
201 .map_or(true, |p| self.try_disable_lint_for_param(p, eval_stack))
203 param.apply_lint.set(false);
215 pub struct OnlyUsedInRecursion {
216 /// Track the top-level body entered. Needed to delay reporting when entering nested bodies.
217 entered_body: Option<HirId>,
221 impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion {
222 fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'tcx>) {
223 if body.value.span.from_expansion() {
226 // `skip_params` is either `0` or `1` to skip the `self` parameter in trait functions.
227 // It can't be renamed, and it can't be removed without removing it from multiple functions.
228 let (fn_id, fn_kind, skip_params) = match get_parent_node(cx.tcx, body.value.hir_id) {
229 Some(Node::Item(i)) => (i.def_id.to_def_id(), FnKind::Fn, 0),
230 Some(Node::TraitItem(&TraitItem {
231 kind: TraitItemKind::Fn(ref sig, _),
237 if sig.decl.implicit_self.has_implicit_self() {
243 Some(Node::ImplItem(&ImplItem {
244 kind: ImplItemKind::Fn(ref sig, _),
248 #[allow(trivial_casts)]
249 if let Some(Node::Item(item)) = get_parent_node(cx.tcx, cx.tcx.hir().local_def_id_to_hir_id(def_id))
250 && let Some(trait_ref) = cx.tcx.impl_trait_ref(item.def_id)
251 && let Some(trait_item_id) = cx.tcx.associated_item(def_id).trait_item_def_id
255 FnKind::ImplTraitFn(cx.tcx.erase_regions(trait_ref.substs) as *const _ as usize),
256 if sig.decl.implicit_self.has_implicit_self() {
263 (def_id.to_def_id(), FnKind::Fn, 0)
272 .filter_map(|(idx, p)| match p.pat.kind {
273 PatKind::Binding(_, id, ident, None) if !ident.as_str().starts_with('_') => {
274 Some((id, Param::new(fn_id, fn_kind, idx, ident)))
278 .for_each(|(id, param)| self.params.insert(param, id));
279 if self.entered_body.is_none() {
280 self.entered_body = Some(body.value.hir_id);
284 fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) {
285 if let Some(id) = path_to_local(e)
286 && let Some(param) = self.params.get_by_id_mut(id)
288 let typeck = cx.typeck_results();
292 match get_expr_use_or_unification_node(cx.tcx, e) {
293 None | Some((Node::Stmt(_), _)) => return,
294 Some((Node::Expr(parent), child_id)) => match parent.kind {
295 // Recursive call. Track which index the parameter is used in.
296 ExprKind::Call(callee, args)
297 if path_def_id(cx, callee).map_or(false, |id| {
299 && has_matching_substs(param.fn_kind, typeck.node_substs(callee.hir_id))
302 if let Some(idx) = args.iter().position(|arg| arg.hir_id == child_id) {
303 param.uses.push(Usage::new(span, idx));
307 ExprKind::MethodCall(_, receiver, args, _)
308 if typeck.type_dependent_def_id(parent.hir_id).map_or(false, |id| {
310 && has_matching_substs(param.fn_kind, typeck.node_substs(parent.hir_id))
313 if let Some(idx) = std::iter::once(receiver).chain(args.iter()).position(|arg| arg.hir_id == child_id) {
314 param.uses.push(Usage::new(span, idx));
318 // Assignment to a parameter is fine.
319 ExprKind::Assign(lhs, _, _) | ExprKind::AssignOp(_, lhs, _) if lhs.hir_id == child_id => {
322 // Parameter update e.g. `x = x + 1`
323 ExprKind::Assign(lhs, rhs, _) | ExprKind::AssignOp(_, lhs, rhs)
324 if rhs.hir_id == child_id && path_to_local_id(lhs, id) =>
328 // Side-effect free expressions. Walk to the parent expression.
329 ExprKind::Binary(_, lhs, rhs)
330 if typeck.expr_ty(lhs).is_primitive() && typeck.expr_ty(rhs).is_primitive() =>
335 ExprKind::Unary(_, arg) if typeck.expr_ty(arg).is_primitive() => {
339 ExprKind::AddrOf(..) | ExprKind::Cast(..) => {
343 // Only allow field accesses without auto-deref
344 ExprKind::Field(..) if typeck.adjustments().get(child_id).is_none() => {
352 self.params.remove_by_id(id);
358 fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'tcx>) {
359 if self.entered_body == Some(body.value.hir_id) {
360 self.entered_body = None;
361 self.params.flag_for_linting();
362 for param in &self.params.params {
363 if param.apply_lint.get() {
366 ONLY_USED_IN_RECURSION,
368 "parameter is only used in recursion",
370 if param.ident.name != kw::SelfLower {
371 diag.span_suggestion(
373 "if this is intentional, prefix it with an underscore",
374 format!("_{}", param.ident.name),
375 Applicability::MaybeIncorrect,
379 param.uses.iter().map(|x| x.span).collect::<Vec<_>>(),
380 "parameter used here",
391 fn has_matching_substs(kind: FnKind, substs: SubstsRef<'_>) -> bool {
394 FnKind::TraitFn => substs.iter().enumerate().all(|(idx, subst)| match subst.unpack() {
395 GenericArgKind::Lifetime(_) => true,
396 GenericArgKind::Type(ty) => matches!(*ty.kind(), ty::Param(ty) if ty.index as usize == idx),
397 GenericArgKind::Const(c) => matches!(c.kind(), ConstKind::Param(c) if c.index as usize == idx),
399 #[allow(trivial_casts)]
400 FnKind::ImplTraitFn(expected_substs) => substs as *const _ as usize == expected_substs,