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;
6
- use rustc_data_structures:: fx:: FxHashSet ;
4
+ use clippy_utils:: { get_parent_node, is_from_proc_macro, is_self} ;
5
+ use rustc_data_structures:: fx:: { FxHashSet , FxIndexMap } ;
7
6
use rustc_errors:: Applicability ;
8
- use rustc_hir:: intravisit:: FnKind ;
9
- 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:: { Body , ExprKind , FnDecl , HirId , HirIdMap , HirIdSet , Impl , ItemKind , Mutability , Node , PatKind , QPath } ;
10
9
use rustc_hir_typeck:: expr_use_visitor as euv;
11
10
use rustc_infer:: infer:: TyCtxtInferExt ;
12
11
use rustc_lint:: { LateContext , LateLintPass } ;
13
12
use rustc_middle:: hir:: map:: associated_body;
13
+ use rustc_middle:: hir:: nested_filter:: OnlyBodies ;
14
14
use rustc_middle:: mir:: FakeReadCause ;
15
15
use rustc_middle:: ty:: { self , Ty , UpvarId , UpvarPath } ;
16
16
use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
@@ -48,20 +48,24 @@ declare_clippy_lint! {
48
48
"using a `&mut` argument when it's not mutated"
49
49
}
50
50
51
- #[ derive( Copy , Clone ) ]
52
- pub struct NeedlessPassByRefMut {
51
+ #[ derive( Clone ) ]
52
+ pub struct NeedlessPassByRefMut < ' tcx > {
53
53
avoid_breaking_exported_api : bool ,
54
+ used_fn_def_ids : FxHashSet < LocalDefId > ,
55
+ fn_def_ids_to_maybe_unused_mut : FxIndexMap < LocalDefId , Vec < rustc_hir:: Ty < ' tcx > > > ,
54
56
}
55
57
56
- impl NeedlessPassByRefMut {
58
+ impl NeedlessPassByRefMut < ' _ > {
57
59
pub fn new ( avoid_breaking_exported_api : bool ) -> Self {
58
60
Self {
59
61
avoid_breaking_exported_api,
62
+ used_fn_def_ids : FxHashSet :: default ( ) ,
63
+ fn_def_ids_to_maybe_unused_mut : FxIndexMap :: default ( ) ,
60
64
}
61
65
}
62
66
}
63
67
64
- impl_lint_pass ! ( NeedlessPassByRefMut => [ NEEDLESS_PASS_BY_REF_MUT ] ) ;
68
+ impl_lint_pass ! ( NeedlessPassByRefMut < ' _> => [ NEEDLESS_PASS_BY_REF_MUT ] ) ;
65
69
66
70
fn should_skip < ' tcx > (
67
71
cx : & LateContext < ' tcx > ,
@@ -89,12 +93,12 @@ fn should_skip<'tcx>(
89
93
is_from_proc_macro ( cx, & input)
90
94
}
91
95
92
- impl < ' tcx > LateLintPass < ' tcx > for NeedlessPassByRefMut {
96
+ impl < ' tcx > LateLintPass < ' tcx > for NeedlessPassByRefMut < ' tcx > {
93
97
fn check_fn (
94
98
& mut self ,
95
99
cx : & LateContext < ' tcx > ,
96
100
kind : FnKind < ' tcx > ,
97
- decl : & ' tcx FnDecl < ' _ > ,
101
+ decl : & ' tcx FnDecl < ' tcx > ,
98
102
body : & ' tcx Body < ' _ > ,
99
103
span : Span ,
100
104
fn_def_id : LocalDefId ,
@@ -140,7 +144,6 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut {
140
144
if it. peek ( ) . is_none ( ) {
141
145
return ;
142
146
}
143
-
144
147
// Collect variables mutably used and spans which will need dereferencings from the
145
148
// function body.
146
149
let MutablyUsedVariablesCtxt { mutably_used_vars, .. } = {
@@ -165,30 +168,45 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut {
165
168
}
166
169
ctx
167
170
} ;
168
-
169
- let show_semver_warning = self . avoid_breaking_exported_api && cx. effective_visibilities . is_exported ( fn_def_id) ;
170
171
for ( ( & input, & _) , arg) in it {
171
172
// Only take `&mut` arguments.
172
- if_chain ! {
173
- if let PatKind :: Binding ( _, canonical_id, ..) = arg. pat. kind;
174
- if !mutably_used_vars. contains( & canonical_id) ;
175
- if let rustc_hir:: TyKind :: Ref ( _, inner_ty) = input. kind;
176
- then {
177
- // If the argument is never used mutably, we emit the warning.
178
- let sp = input. span;
179
- span_lint_and_then(
173
+ if let PatKind :: Binding ( _, canonical_id, ..) = arg. pat . kind
174
+ && !mutably_used_vars. contains ( & canonical_id)
175
+ {
176
+ self . fn_def_ids_to_maybe_unused_mut . entry ( fn_def_id) . or_default ( ) . push ( input) ;
177
+ }
178
+ }
179
+ }
180
+
181
+ fn check_crate_post ( & mut self , cx : & LateContext < ' tcx > ) {
182
+ cx. tcx . hir ( ) . visit_all_item_likes_in_crate ( & mut FnNeedsMutVisitor {
183
+ cx,
184
+ used_fn_def_ids : & mut self . used_fn_def_ids ,
185
+ } ) ;
186
+
187
+ for ( fn_def_id, unused) in self
188
+ . fn_def_ids_to_maybe_unused_mut
189
+ . iter ( )
190
+ . filter ( |( def_id, _) | !self . used_fn_def_ids . contains ( def_id) )
191
+ {
192
+ let show_semver_warning =
193
+ self . avoid_breaking_exported_api && cx. effective_visibilities . is_exported ( * fn_def_id) ;
194
+
195
+ for input in unused {
196
+ // If the argument is never used mutably, we emit the warning.
197
+ let sp = input. span ;
198
+ if let rustc_hir:: TyKind :: Ref ( _, inner_ty) = input. kind {
199
+ span_lint_hir_and_then (
180
200
cx,
181
201
NEEDLESS_PASS_BY_REF_MUT ,
202
+ cx. tcx . hir ( ) . local_def_id_to_hir_id ( * fn_def_id) ,
182
203
sp,
183
204
"this argument is a mutable reference, but not used mutably" ,
184
205
|diag| {
185
206
diag. span_suggestion (
186
207
sp,
187
208
"consider changing to" . to_string ( ) ,
188
- format!(
189
- "&{}" ,
190
- snippet( cx, cx. tcx. hir( ) . span( inner_ty. ty. hir_id) , "_" ) ,
191
- ) ,
209
+ format ! ( "&{}" , snippet( cx, cx. tcx. hir( ) . span( inner_ty. ty. hir_id) , "_" ) , ) ,
192
210
Applicability :: Unspecified ,
193
211
) ;
194
212
if show_semver_warning {
@@ -316,3 +334,44 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt {
316
334
self . prev_bind = Some ( id) ;
317
335
}
318
336
}
337
+
338
+ /// A final pass to check for paths referencing this function that require the argument to be
339
+ /// `&mut`, basically if the function is ever used as a `fn`-like argument.
340
+ struct FnNeedsMutVisitor < ' a , ' tcx > {
341
+ cx : & ' a LateContext < ' tcx > ,
342
+ used_fn_def_ids : & ' a mut FxHashSet < LocalDefId > ,
343
+ }
344
+
345
+ impl < ' tcx > Visitor < ' tcx > for FnNeedsMutVisitor < ' _ , ' tcx > {
346
+ type NestedFilter = OnlyBodies ;
347
+
348
+ fn nested_visit_map ( & mut self ) -> Self :: Map {
349
+ self . cx . tcx . hir ( )
350
+ }
351
+
352
+ fn visit_qpath ( & mut self , qpath : & ' tcx QPath < ' tcx > , hir_id : HirId , _: Span ) {
353
+ walk_qpath ( self , qpath, hir_id) ;
354
+
355
+ let Self { cx, used_fn_def_ids } = self ;
356
+
357
+ // #11182; do not lint if mutability is required elsewhere
358
+ if let Node :: Expr ( expr) = cx. tcx . hir ( ) . get ( hir_id)
359
+ && let Some ( parent) = get_parent_node ( cx. tcx , expr. hir_id )
360
+ && let ty:: FnDef ( def_id, _) = cx. tcx . typeck ( cx. tcx . hir ( ) . enclosing_body_owner ( hir_id) ) . expr_ty ( expr) . kind ( )
361
+ && let Some ( def_id) = def_id. as_local ( )
362
+ {
363
+ if let Node :: Expr ( e) = parent
364
+ && let ExprKind :: Call ( call, _) = e. kind
365
+ && call. hir_id == expr. hir_id
366
+ {
367
+ return ;
368
+ }
369
+
370
+ // We don't need to check each argument individually as you cannot coerce a function
371
+ // taking `&mut` -> `&`, for some reason, so if we've gotten this far we know it's
372
+ // passed as a `fn`-like argument (or is unified) and should ignore every "unused"
373
+ // argument entirely
374
+ used_fn_def_ids. insert ( def_id) ;
375
+ }
376
+ }
377
+ }
0 commit comments