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