11use 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 ;
33use 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 } ;
76use 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 } ;
109use rustc_hir_typeck:: expr_use_visitor as euv;
1110use rustc_infer:: infer:: TyCtxtInferExt ;
1211use rustc_lint:: { LateContext , LateLintPass } ;
1312use rustc_middle:: hir:: map:: associated_body;
13+ use rustc_middle:: hir:: nested_filter:: OnlyBodies ;
1414use rustc_middle:: mir:: FakeReadCause ;
1515use rustc_middle:: ty:: { self , Ty , UpvarId , UpvarPath } ;
1616use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
@@ -48,20 +48,24 @@ declare_clippy_lint! {
4848 "using a `&mut` argument when it's not mutated"
4949}
5050
51- #[ derive( Copy , Clone ) ]
52- pub struct NeedlessPassByRefMut {
51+ #[ derive( Clone ) ]
52+ pub struct NeedlessPassByRefMut < ' tcx > {
5353 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 > > > ,
5456}
5557
56- impl NeedlessPassByRefMut {
58+ impl NeedlessPassByRefMut < ' _ > {
5759 pub fn new ( avoid_breaking_exported_api : bool ) -> Self {
5860 Self {
5961 avoid_breaking_exported_api,
62+ used_fn_def_ids : FxHashSet :: default ( ) ,
63+ fn_def_ids_to_maybe_unused_mut : FxIndexMap :: default ( ) ,
6064 }
6165 }
6266}
6367
64- impl_lint_pass ! ( NeedlessPassByRefMut => [ NEEDLESS_PASS_BY_REF_MUT ] ) ;
68+ impl_lint_pass ! ( NeedlessPassByRefMut < ' _> => [ NEEDLESS_PASS_BY_REF_MUT ] ) ;
6569
6670fn should_skip < ' tcx > (
6771 cx : & LateContext < ' tcx > ,
@@ -89,12 +93,12 @@ fn should_skip<'tcx>(
8993 is_from_proc_macro ( cx, & input)
9094}
9195
92- impl < ' tcx > LateLintPass < ' tcx > for NeedlessPassByRefMut {
96+ impl < ' tcx > LateLintPass < ' tcx > for NeedlessPassByRefMut < ' tcx > {
9397 fn check_fn (
9498 & mut self ,
9599 cx : & LateContext < ' tcx > ,
96100 kind : FnKind < ' tcx > ,
97- decl : & ' tcx FnDecl < ' _ > ,
101+ decl : & ' tcx FnDecl < ' tcx > ,
98102 body : & ' tcx Body < ' _ > ,
99103 span : Span ,
100104 fn_def_id : LocalDefId ,
@@ -140,7 +144,6 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut {
140144 if it. peek ( ) . is_none ( ) {
141145 return ;
142146 }
143-
144147 // Collect variables mutably used and spans which will need dereferencings from the
145148 // function body.
146149 let MutablyUsedVariablesCtxt { mutably_used_vars, .. } = {
@@ -165,30 +168,45 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut {
165168 }
166169 ctx
167170 } ;
168-
169- let show_semver_warning = self . avoid_breaking_exported_api && cx. effective_visibilities . is_exported ( fn_def_id) ;
170171 for ( ( & input, & _) , arg) in it {
171172 // 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 (
180200 cx,
181201 NEEDLESS_PASS_BY_REF_MUT ,
202+ cx. tcx . hir ( ) . local_def_id_to_hir_id ( * fn_def_id) ,
182203 sp,
183204 "this argument is a mutable reference, but not used mutably" ,
184205 |diag| {
185206 diag. span_suggestion (
186207 sp,
187208 "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) , "_" ) , ) ,
192210 Applicability :: Unspecified ,
193211 ) ;
194212 if show_semver_warning {
@@ -316,3 +334,44 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt {
316334 self . prev_bind = Some ( id) ;
317335 }
318336}
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