|
1 | 1 | use super::needless_pass_by_value::requires_exact_signature;
|
2 |
| -use clippy_utils::diagnostics::span_lint_and_then; |
| 2 | +use clippy_utils::diagnostics::span_lint_hir_and_then; |
3 | 3 | use clippy_utils::source::snippet;
|
4 |
| -use clippy_utils::{is_from_proc_macro, is_self}; |
5 |
| -use if_chain::if_chain; |
| 4 | +use clippy_utils::{get_parent_node, is_from_proc_macro, is_self}; |
| 5 | +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; |
6 | 6 | use rustc_errors::Applicability;
|
7 |
| -use rustc_hir::intravisit::FnKind; |
8 |
| -use rustc_hir::{Body, FnDecl, HirId, HirIdMap, HirIdSet, Impl, ItemKind, Mutability, Node, PatKind}; |
| 7 | +use rustc_hir::intravisit::{walk_qpath, FnKind, Visitor}; |
| 8 | +use rustc_hir::{ |
| 9 | + Body, ExprField, ExprKind, FnDecl, HirId, HirIdMap, HirIdSet, Impl, ItemKind, Mutability, Node, PatKind, QPath, |
| 10 | +}; |
9 | 11 | use rustc_hir_typeck::expr_use_visitor as euv;
|
10 | 12 | use rustc_infer::infer::TyCtxtInferExt;
|
11 | 13 | use rustc_lint::{LateContext, LateLintPass};
|
| 14 | +use rustc_middle::hir::nested_filter::OnlyBodies; |
12 | 15 | use rustc_middle::mir::FakeReadCause;
|
13 | 16 | use rustc_middle::ty::{self, Ty};
|
14 | 17 | use rustc_session::{declare_tool_lint, impl_lint_pass};
|
@@ -46,20 +49,26 @@ declare_clippy_lint! {
|
46 | 49 | "using a `&mut` argument when it's not mutated"
|
47 | 50 | }
|
48 | 51 |
|
49 |
| -#[derive(Copy, Clone)] |
50 |
| -pub struct NeedlessPassByRefMut { |
| 52 | +#[derive(Clone)] |
| 53 | +pub struct NeedlessPassByRefMut<'tcx> { |
51 | 54 | avoid_breaking_exported_api: bool,
|
| 55 | + used_fn_def_ids: FxHashSet<LocalDefId>, |
| 56 | + // TODO(Centri3): Change this to a `Vec`. This will cause a performance deficit but it'll make the output a lot |
| 57 | + // more consistent, as adding or removing a function won't change the order of them |
| 58 | + fn_def_ids_to_maybe_unused_mut: FxHashMap<LocalDefId, Vec<rustc_hir::Ty<'tcx>>>, |
52 | 59 | }
|
53 | 60 |
|
54 |
| -impl NeedlessPassByRefMut { |
| 61 | +impl NeedlessPassByRefMut<'_> { |
55 | 62 | pub fn new(avoid_breaking_exported_api: bool) -> Self {
|
56 | 63 | Self {
|
57 | 64 | avoid_breaking_exported_api,
|
| 65 | + used_fn_def_ids: FxHashSet::default(), |
| 66 | + fn_def_ids_to_maybe_unused_mut: FxHashMap::default(), |
58 | 67 | }
|
59 | 68 | }
|
60 | 69 | }
|
61 | 70 |
|
62 |
| -impl_lint_pass!(NeedlessPassByRefMut => [NEEDLESS_PASS_BY_REF_MUT]); |
| 71 | +impl_lint_pass!(NeedlessPassByRefMut<'_> => [NEEDLESS_PASS_BY_REF_MUT]); |
63 | 72 |
|
64 | 73 | fn should_skip<'tcx>(
|
65 | 74 | cx: &LateContext<'tcx>,
|
@@ -87,12 +96,12 @@ fn should_skip<'tcx>(
|
87 | 96 | is_from_proc_macro(cx, &input)
|
88 | 97 | }
|
89 | 98 |
|
90 |
| -impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut { |
| 99 | +impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> { |
91 | 100 | fn check_fn(
|
92 | 101 | &mut self,
|
93 | 102 | cx: &LateContext<'tcx>,
|
94 | 103 | kind: FnKind<'tcx>,
|
95 |
| - decl: &'tcx FnDecl<'_>, |
| 104 | + decl: &'tcx FnDecl<'tcx>, |
96 | 105 | body: &'tcx Body<'_>,
|
97 | 106 | span: Span,
|
98 | 107 | fn_def_id: LocalDefId,
|
@@ -157,29 +166,45 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut {
|
157 | 166 | if it.peek().is_none() {
|
158 | 167 | return;
|
159 | 168 | }
|
160 |
| - let show_semver_warning = self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(fn_def_id); |
161 | 169 | for ((&input, &_), arg) in it {
|
162 | 170 | // Only take `&mut` arguments.
|
163 |
| - if_chain! { |
164 |
| - if let PatKind::Binding(_, canonical_id, ..) = arg.pat.kind; |
165 |
| - if !mutably_used_vars.contains(&canonical_id); |
166 |
| - if let rustc_hir::TyKind::Ref(_, inner_ty) = input.kind; |
167 |
| - then { |
168 |
| - // If the argument is never used mutably, we emit the warning. |
169 |
| - let sp = input.span; |
170 |
| - span_lint_and_then( |
| 171 | + if let PatKind::Binding(_, canonical_id, ..) = arg.pat.kind |
| 172 | + && !mutably_used_vars.contains(&canonical_id) |
| 173 | + { |
| 174 | + self.fn_def_ids_to_maybe_unused_mut.entry(fn_def_id).or_insert(vec![]).push(input); |
| 175 | + } |
| 176 | + } |
| 177 | + } |
| 178 | + |
| 179 | + fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { |
| 180 | + cx.tcx.hir().visit_all_item_likes_in_crate(&mut FnNeedsMutVisitor { |
| 181 | + cx, |
| 182 | + used_fn_def_ids: &mut self.used_fn_def_ids, |
| 183 | + }); |
| 184 | + |
| 185 | + for (fn_def_id, unused) in self |
| 186 | + .fn_def_ids_to_maybe_unused_mut |
| 187 | + .iter() |
| 188 | + .filter(|(def_id, _)| !self.used_fn_def_ids.contains(def_id)) |
| 189 | + { |
| 190 | + let show_semver_warning = |
| 191 | + self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(*fn_def_id); |
| 192 | + |
| 193 | + for input in unused { |
| 194 | + // If the argument is never used mutably, we emit the warning. |
| 195 | + let sp = input.span; |
| 196 | + if let rustc_hir::TyKind::Ref(_, inner_ty) = input.kind { |
| 197 | + span_lint_hir_and_then( |
171 | 198 | cx,
|
172 | 199 | NEEDLESS_PASS_BY_REF_MUT,
|
| 200 | + cx.tcx.hir().local_def_id_to_hir_id(*fn_def_id), |
173 | 201 | sp,
|
174 | 202 | "this argument is a mutable reference, but not used mutably",
|
175 | 203 | |diag| {
|
176 | 204 | diag.span_suggestion(
|
177 | 205 | sp,
|
178 | 206 | "consider changing to".to_string(),
|
179 |
| - format!( |
180 |
| - "&{}", |
181 |
| - snippet(cx, cx.tcx.hir().span(inner_ty.ty.hir_id), "_"), |
182 |
| - ), |
| 207 | + format!("&{}", snippet(cx, cx.tcx.hir().span(inner_ty.ty.hir_id), "_"),), |
183 | 208 | Applicability::Unspecified,
|
184 | 209 | );
|
185 | 210 | if show_semver_warning {
|
@@ -271,3 +296,49 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt {
|
271 | 296 | self.prev_bind = Some(id);
|
272 | 297 | }
|
273 | 298 | }
|
| 299 | + |
| 300 | +/// A final pass to check for paths referencing this function that require the argument to be |
| 301 | +/// `&mut`, basically if the function is ever used as a `fn`-like argument. |
| 302 | +struct FnNeedsMutVisitor<'a, 'tcx> { |
| 303 | + cx: &'a LateContext<'tcx>, |
| 304 | + used_fn_def_ids: &'a mut FxHashSet<LocalDefId>, |
| 305 | +} |
| 306 | + |
| 307 | +impl<'tcx> Visitor<'tcx> for FnNeedsMutVisitor<'_, 'tcx> { |
| 308 | + type NestedFilter = OnlyBodies; |
| 309 | + |
| 310 | + fn nested_visit_map(&mut self) -> Self::Map { |
| 311 | + self.cx.tcx.hir() |
| 312 | + } |
| 313 | + |
| 314 | + fn visit_qpath(&mut self, qpath: &'tcx QPath<'tcx>, hir_id: HirId, _: Span) { |
| 315 | + walk_qpath(self, qpath, hir_id); |
| 316 | + |
| 317 | + let Self { cx, used_fn_def_ids } = self; |
| 318 | + |
| 319 | + if let Node::Expr(expr) = cx.tcx.hir().get(hir_id) |
| 320 | + && let Some(parent) = get_parent_node(cx.tcx, expr.hir_id) |
| 321 | + && let ty::FnDef(def_id, _) = cx.tcx.typeck(cx.tcx.hir().enclosing_body_owner(hir_id)).expr_ty(expr).kind() |
| 322 | + && let Some(def_id) = def_id.as_local() |
| 323 | + { |
| 324 | + let Some(e) = (match parent { |
| 325 | + // #11182 |
| 326 | + Node::Expr(e) => Some(e), |
| 327 | + // #11199 |
| 328 | + Node::ExprField(ExprField { expr, .. }) => Some(*expr), |
| 329 | + _ => None, |
| 330 | + }) else { |
| 331 | + return; |
| 332 | + }; |
| 333 | + |
| 334 | + if matches!(e.kind, ExprKind::Call(call, _) if call.hir_id == expr.hir_id) { |
| 335 | + return; |
| 336 | + } |
| 337 | + |
| 338 | + // We don't need to check each argument individually as you cannot coerce a function |
| 339 | + // taking `&mut` -> `&`, for some reason, so if we've gotten this far we know it's |
| 340 | + // passed as a `fn`-like argument and should ignore every "unused" argument entirely |
| 341 | + used_fn_def_ids.insert(def_id); |
| 342 | + } |
| 343 | + } |
| 344 | +} |
0 commit comments