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:: {
9+ Body , ExprField , ExprKind , FnDecl , HirId , HirIdMap , HirIdSet , Impl , ItemKind , Mutability , Node , PatKind , QPath ,
10+ } ;
1011use rustc_hir_typeck:: expr_use_visitor as euv;
1112use rustc_infer:: infer:: TyCtxtInferExt ;
1213use rustc_lint:: { LateContext , LateLintPass } ;
1314use rustc_middle:: hir:: map:: associated_body;
15+ use rustc_middle:: hir:: nested_filter:: OnlyBodies ;
1416use rustc_middle:: mir:: FakeReadCause ;
1517use rustc_middle:: ty:: { self , Ty , UpvarId , UpvarPath } ;
1618use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
@@ -48,20 +50,24 @@ declare_clippy_lint! {
4850 "using a `&mut` argument when it's not mutated"
4951}
5052
51- #[ derive( Copy , Clone ) ]
52- pub struct NeedlessPassByRefMut {
53+ #[ derive( Clone ) ]
54+ pub struct NeedlessPassByRefMut < ' tcx > {
5355 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 > > > ,
5458}
5559
56- impl NeedlessPassByRefMut {
60+ impl NeedlessPassByRefMut < ' _ > {
5761 pub fn new ( avoid_breaking_exported_api : bool ) -> Self {
5862 Self {
5963 avoid_breaking_exported_api,
64+ used_fn_def_ids : FxHashSet :: default ( ) ,
65+ fn_def_ids_to_maybe_unused_mut : FxIndexMap :: default ( ) ,
6066 }
6167 }
6268}
6369
64- impl_lint_pass ! ( NeedlessPassByRefMut => [ NEEDLESS_PASS_BY_REF_MUT ] ) ;
70+ impl_lint_pass ! ( NeedlessPassByRefMut < ' _> => [ NEEDLESS_PASS_BY_REF_MUT ] ) ;
6571
6672fn should_skip < ' tcx > (
6773 cx : & LateContext < ' tcx > ,
@@ -89,12 +95,12 @@ fn should_skip<'tcx>(
8995 is_from_proc_macro ( cx, & input)
9096}
9197
92- impl < ' tcx > LateLintPass < ' tcx > for NeedlessPassByRefMut {
98+ impl < ' tcx > LateLintPass < ' tcx > for NeedlessPassByRefMut < ' tcx > {
9399 fn check_fn (
94100 & mut self ,
95101 cx : & LateContext < ' tcx > ,
96102 kind : FnKind < ' tcx > ,
97- decl : & ' tcx FnDecl < ' _ > ,
103+ decl : & ' tcx FnDecl < ' tcx > ,
98104 body : & ' tcx Body < ' _ > ,
99105 span : Span ,
100106 fn_def_id : LocalDefId ,
@@ -140,7 +146,6 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut {
140146 if it. peek ( ) . is_none ( ) {
141147 return ;
142148 }
143-
144149 // Collect variables mutably used and spans which will need dereferencings from the
145150 // function body.
146151 let MutablyUsedVariablesCtxt { mutably_used_vars, .. } = {
@@ -165,30 +170,45 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut {
165170 }
166171 ctx
167172 } ;
168-
169- let show_semver_warning = self . avoid_breaking_exported_api && cx. effective_visibilities . is_exported ( fn_def_id) ;
170173 for ( ( & input, & _) , arg) in it {
171174 // 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 (
180202 cx,
181203 NEEDLESS_PASS_BY_REF_MUT ,
204+ cx. tcx . hir ( ) . local_def_id_to_hir_id ( * fn_def_id) ,
182205 sp,
183206 "this argument is a mutable reference, but not used mutably" ,
184207 |diag| {
185208 diag. span_suggestion (
186209 sp,
187210 "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) , "_" ) , ) ,
192212 Applicability :: Unspecified ,
193213 ) ;
194214 if show_semver_warning {
@@ -316,3 +336,49 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt {
316336 self . prev_bind = Some ( id) ;
317337 }
318338}
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